﻿using System;
public class PPU
{

    public virtual int Mirroring
    {
        set
        {
            setMirroring(value);
        }
    }

    private NES nes;
    private Memory ppuMem;
    private Memory sprMem;


    // Rendering Options:
    bool showSpr0Hit = false;
    bool showSoundBuffer = false;
    bool clipToTvSize = true;


    // Control Flags Register 1:
    public int f_nmiOnVblank;    // NMI on VBlank. 0=disable, 1=enable
    public int f_spriteSize;     // Sprite size. 0=8x8, 1=8x16
    public int f_bgPatternTable; // Background Pattern Table address. 0=0x0000,1=0x1000
    public int f_spPatternTable; // Sprite Pattern Table address. 0=0x0000,1=0x1000
    public int f_addrInc;        // PPU Address Increment. 0=1,1=32
    public int f_nTblAddress;    // Name Table Address. 0=0x2000,1=0x2400,2=0x2800,3=0x2C00


    // Control Flags Register 2:
    public int f_color;	   	 	 // Background color. 0=black, 1=blue, 2=green, 4=red
    public int f_spVisibility;   // Sprite visibility. 0=not displayed,1=displayed
    public int f_bgVisibility;   // Background visibility. 0=Not Displayed,1=displayed
    public int f_spClipping;     // Sprite clipping. 0=Sprites invisible in left 8-pixel column,1=No clipping
    public int f_bgClipping;     // Background clipping. 0=BG invisible in left 8-pixel column, 1=No clipping
    public int f_dispType;       // Display type. 0=color, 1=monochrome


    // Status flags:
    public int STATUS_VRAMWRITE = 4;
    public int STATUS_SLSPRITECOUNT = 5;
    public int STATUS_SPRITE0HIT = 6;
    public int STATUS_VBLANK = 7;


    // VRAM I/O:
    int vramAddress;
    int vramTmpAddress;
    short vramBufferedReadValue;
    bool firstWrite = true; 		// VRAM/Scroll Hi/Lo latch


    int[] vramMirrorTable; 			// Mirroring Lookup Table.
    int i;

    // SPR-RAM I/O:
    short sramAddress; // 8-bit only.

    // Counters:
    int cntFV;
    int cntV;
    int cntH;
    int cntVT;
    int cntHT;

    // Registers:
    int regFV;
    int regV;
    int regH;
    int regVT;
    int regHT;
    int regFH;
    int regS;

    // VBlank extension for PAL emulation:
    int vblankAdd = 0;

    public int curX;
    public int scanline;
    public int lastRenderedScanline;
    public int mapperIrqCounter;


    // Sprite data:
    public int[] sprX;				// X coordinate
    public int[] sprY;				// Y coordinate
    public int[] sprTile;			// Tile Index (into pattern table)
    public int[] sprCol;			// Upper two bits of color
    public bool[] vertFlip;		// Vertical Flip
    public bool[] horiFlip;		// Horizontal Flip
    public bool[] bgPriority;	// Background priority
    public int spr0HitX;	// Sprite #0 hit X coordinate
    public int spr0HitY;	// Sprite #0 hit Y coordinate
    bool hitSpr0;

    // Tiles:
    public Tile[] ptTile;


    // Name table data:
    int[] ntable1 = new int[4];
    NameTable[] nameTable;
    int currentMirroring = -1;

    // Palette data:
    int[] sprPalette = new int[16];
    int[] imgPalette = new int[16];


    // Misc:
    bool scanlineAlreadyRendered;
    bool requestEndFrame;
    bool nmiOk;
    int nmiCounter;
    short tmp;
    bool dummyCycleToggle;

    // Vars used when updating regs/address:
    int address, b1, b2;




    // Variables used when rendering:
    int[] attrib = new int[32];
    int[] bgbuffer = new int[256 * 240];
    int[] pixrendered = new int[256 * 240];
    int[] spr0dummybuffer = new int[256 * 240];
    int[] dummyPixPriTable = new int[256 * 240];
    int[] oldFrame = new int[256 * 240];
    internal int[] buffer;
    int[] tpix;

    bool[] scanlineChanged = new bool[240];
    internal bool requestRenderAll = false;
    bool validTileData;
    int att;

    Tile[] scantile = new Tile[32];
    Tile t;




    // These are temporary variables used in rendering and sound procedures.
    // Their states outside of those procedures can be ignored.
    int curNt;
    int destIndex;
    int x, y, sx;
    int si, ei;
    int tile;
    int col;
    int baseTile;
    int tscanoffset;
    int srcy1, srcy2;
    int bufferSize, available, scale;

    public int cycles = 0;


    public PPU(NES nes)
    {
        this.nes = nes;
    }

    public void init()
    {

        // Get the memory:
        ppuMem = nes.PpuMemory;
        sprMem = nes.SprMemory;

        updateControlReg1(0);
        updateControlReg2(0);

        // Initialize misc vars:
        scanline = 0;
        //timer = nes.Gui.Timer;

        // Create sprite arrays:
        sprX = new int[64];
        sprY = new int[64];
        sprTile = new int[64];
        sprCol = new int[64];
        vertFlip = new bool[64];
        horiFlip = new bool[64];
        bgPriority = new bool[64];

        // Create pattern table tile buffers:
        if (ptTile == null)
        {
            ptTile = new Tile[512];
            for (int i = 0; i < 512; i++)
            {
                ptTile[i] = new Tile();
            }
        }

        // Create nametable buffers:
        nameTable = new NameTable[4];
        for (int i = 0; i < 4; i++)
        {
            nameTable[i] = new NameTable(32, 32, "Nt" + i);
        }

        // Initialize mirroring lookup table:
        vramMirrorTable = new int[0x8000];
        for (int i = 0; i < 0x8000; i++)
        {
            vramMirrorTable[i] = i;
        }

        lastRenderedScanline = -1;
        curX = 0;

        // Initialize old frame buffer:
        for (int i = 0; i < oldFrame.Length; i++)
        {
            oldFrame[i] = -1;
        }

    }


    // Sets Nametable mirroring.
    public void setMirroring(int mirroring)
    {

        if (mirroring == currentMirroring)
        {
            return;
        }

        currentMirroring = mirroring;
        triggerRendering();

        // Remove mirroring:
        if (vramMirrorTable == null)
        {
            vramMirrorTable = new int[0x8000];
        }
        for (int i = 0; i < 0x8000; i++)
        {
            vramMirrorTable[i] = i;
        }

        // Palette mirroring:
        defineMirrorRegion(0x3f20, 0x3f00, 0x20);
        defineMirrorRegion(0x3f40, 0x3f00, 0x20);
        defineMirrorRegion(0x3f80, 0x3f00, 0x20);
        defineMirrorRegion(0x3fc0, 0x3f00, 0x20);

        // Additional mirroring:
        defineMirrorRegion(0x3000, 0x2000, 0xf00);
        defineMirrorRegion(0x4000, 0x0000, 0x4000);

        if (mirroring == ROM.HORIZONTAL_MIRRORING)
        {


            // Horizontal mirroring.

            ntable1[0] = 0;
            ntable1[1] = 0;
            ntable1[2] = 1;
            ntable1[3] = 1;

            defineMirrorRegion(0x2400, 0x2000, 0x400);
            defineMirrorRegion(0x2c00, 0x2800, 0x400);

        }
        else if (mirroring == ROM.VERTICAL_MIRRORING)
        {

            // Vertical mirroring.

            ntable1[0] = 0;
            ntable1[1] = 1;
            ntable1[2] = 0;
            ntable1[3] = 1;

            defineMirrorRegion(0x2800, 0x2000, 0x400);
            defineMirrorRegion(0x2c00, 0x2400, 0x400);

        }
        else if (mirroring == ROM.SINGLESCREEN_MIRRORING)
        {

            // Single Screen mirroring

            ntable1[0] = 0;
            ntable1[1] = 0;
            ntable1[2] = 0;
            ntable1[3] = 0;

            defineMirrorRegion(0x2400, 0x2000, 0x400);
            defineMirrorRegion(0x2800, 0x2000, 0x400);
            defineMirrorRegion(0x2c00, 0x2000, 0x400);

        }
        else if (mirroring == ROM.SINGLESCREEN_MIRRORING2)
        {


            ntable1[0] = 1;
            ntable1[1] = 1;
            ntable1[2] = 1;
            ntable1[3] = 1;

            defineMirrorRegion(0x2400, 0x2400, 0x400);
            defineMirrorRegion(0x2800, 0x2400, 0x400);
            defineMirrorRegion(0x2c00, 0x2400, 0x400);

        }
        else
        {

            // Assume Four-screen mirroring.

            ntable1[0] = 0;
            ntable1[1] = 1;
            ntable1[2] = 2;
            ntable1[3] = 3;

        }

    }


    // Define a mirrored area in the address lookup table.
    // Assumes the regions don't overlap.
    // The 'to' region is the region that is physically in memory.
    private void defineMirrorRegion(int fromStart, int toStart, int size)
    {

        for (int i = 0; i < size; i++)
        {
            vramMirrorTable[fromStart + i] = toStart + i;
        }

    }



    // Emulates PPU cycles
    public void emulateCycles()
    {

        //int n = (!requestEndFrame && curX+cycles<341 && (scanline-20 < spr0HitY || scanline-22 > spr0HitY))?cycles:1;
        for (; cycles > 0; cycles--)
        {

            if (scanline - 21 == spr0HitY)
            {

                if ((curX == spr0HitX) && (f_spVisibility == 1))
                {
                    // Set sprite 0 hit flag:
                    setStatusFlag(STATUS_SPRITE0HIT, true);
                }

            }

            if (requestEndFrame)
            {
                nmiCounter--;
                if (nmiCounter == 0)
                {
                    requestEndFrame = false;
                    startVBlank();
                }
            }

            curX++;
            if (curX == 341)
            {

                curX = 0;
                endScanline();

            }

        }

    }

    public void startVBlank()
    {

        // Start VBlank period:
        // Do VBlank.

        // Do NMI:
        nes.Cpu.requestIrq(CPU.IRQ_NMI);

        // Make sure everything is rendered:
        if (lastRenderedScanline < 239)
        {
            renderFramePartially(nes.gui.ScreenView.Buffer, lastRenderedScanline + 1, 240 - lastRenderedScanline);
        }

        endFrame();

        // Notify image buffer:
        nes.Gui.ScreenView.imageReady(false);

        // Reset scanline counter:
        lastRenderedScanline = -1;

        startFrame();

    }

    public void endScanline()
    {

        if (scanline < 19 + vblankAdd)
        {

            // VINT
            // do nothing.

        }
        else if (scanline == 19 + vblankAdd)
        {

            // Dummy scanline.
            // May be variable length:
            if (dummyCycleToggle)
            {

                // Remove dead cycle at end of scanline,
                // for next scanline:
                curX = 1;
                dummyCycleToggle = !dummyCycleToggle;

            }

        }
        else if (scanline == 20 + vblankAdd)
        {


            // Clear VBlank flag:
            setStatusFlag(STATUS_VBLANK, false);

            // Clear Sprite #0 hit flag:
            setStatusFlag(STATUS_SPRITE0HIT, false);
            hitSpr0 = false;
            spr0HitX = -1;
            spr0HitY = -1;

            if (f_bgVisibility == 1 || f_spVisibility == 1)
            {

                // Update counters:
                cntFV = regFV;
                cntV = regV;
                cntH = regH;
                cntVT = regVT;
                cntHT = regHT;

                if (f_bgVisibility == 1)
                {
                    // Render dummy scanline:
                    renderBgScanline(buffer, 0);
                }

            }

            if (f_bgVisibility == 1 && f_spVisibility == 1)
            {

                // Check sprite 0 hit for first scanline:
                checkSprite0(0);

            }

            if (f_bgVisibility == 1 || f_spVisibility == 1)
            {
                // Clock mapper IRQ Counter:
                nes.memMapper.clockIrqCounter();
            }

        }
        else if (scanline >= 21 + vblankAdd && scanline <= 260)
        {

            // Render normally:
            if (f_bgVisibility == 1)
            {

                if (!scanlineAlreadyRendered)
                {
                    // update scroll:
                    cntHT = regHT;
                    cntH = regH;
                    renderBgScanline(bgbuffer, scanline + 1 - 21);
                }
                scanlineAlreadyRendered = false;

                // Check for sprite 0 (next scanline):
                if (!hitSpr0 && f_spVisibility == 1)
                {
                    if (sprX[0] >= -7 && sprX[0] < 256 && sprY[0] + 1 <= (scanline - vblankAdd + 1 - 21) && (sprY[0] + 1 + (f_spriteSize == 0 ? 8 : 16)) >= (scanline - vblankAdd + 1 - 21))
                    {
                        if (checkSprite0(scanline + vblankAdd + 1 - 21))
                        {
                            ////System.out.println("found spr0. curscan="+scanline+" hitscan="+spr0HitY);
                            hitSpr0 = true;
                        }
                    }
                }

            }

            if (f_bgVisibility == 1 || f_spVisibility == 1)
            {
                // Clock mapper IRQ Counter:
                nes.memMapper.clockIrqCounter();
            }

        }
        else if (scanline == 261 + vblankAdd)
        {

            // Dead scanline, no rendering.
            // Set VINT:
            setStatusFlag(STATUS_VBLANK, true);
            requestEndFrame = true;
            nmiCounter = 9;

            // Wrap around:
            scanline = -1;	// will be incremented to 0

        }

        scanline++;
        regsToAddress();
        cntsToAddress();

    }

    public void startFrame()
    {

        int[] buffer = nes.Gui.ScreenView.Buffer;

        // Set background color:
        int bgColor = 0;

        if (f_dispType == 0)
        {

            // Color display.
            // f_color determines color emphasis.
            // Use first entry of image palette as BG color.
            bgColor = imgPalette[0];

        }
        else
        {

            // Monochrome display.
            // f_color determines the bg color.
            switch (f_color)
            {

                case 0:
                    {
                        // Black
                        bgColor = 0x00000;
                        break;
                    }
                case 1:
                    {
                        // Green
                        bgColor = 0x00FF00;
                        break;
                    }
                case 2:
                    {
                        // Blue
                        bgColor = 0xFF0000;
                        break;
                    }
                case 3:
                    {
                        // Invalid. Use black.
                        bgColor = 0x000000;
                        break;
                    }
                case 4:
                    {
                        // Red
                        bgColor = 0x0000FF;
                        break;
                    }
                default:
                    {
                        // Invalid. Use black.
                        bgColor = 0x0;
                        break;
                    }
            }

        }

        for (int i = 0; i < buffer.Length; i++) buffer[i] = bgColor;
        for (int i = 0; i < pixrendered.Length; i++) pixrendered[i] = 65;

    }

    public void endFrame()
    {

        int[] buffer = nes.Gui.ScreenView.Buffer;

        // Draw spr#0 hit coordinates:
        if (showSpr0Hit)
        {
            // Spr 0 position:
            if (sprX[0] >= 0 && sprX[0] < 256 && sprY[0] >= 0 && sprY[0] < 240)
            {
                for (int i = 0; i < 256; i++)
                {
                    buffer[(sprY[0] << 8) + i] = 0xFF5555;
                }
                for (int i = 0; i < 240; i++)
                {
                    buffer[(i << 8) + sprX[0]] = 0xFF5555;
                }
            }
            // Hit position:
            if (spr0HitX >= 0 && spr0HitX < 256 && spr0HitY >= 0 && spr0HitY < 240)
            {
                for (int i = 0; i < 256; i++)
                {
                    buffer[(spr0HitY << 8) + i] = 0x55FF55;
                }
                for (int i = 0; i < 240; i++)
                {
                    buffer[(i << 8) + spr0HitX] = 0x55FF55;
                }
            }
        }

        // This is a bit lazy..
        // if either the sprites or the background should be clipped,
        // both are clipped after rendering is finished.
        if (clipToTvSize || f_bgClipping == 0 || f_spClipping == 0)
        {
            // Clip left 8-pixels column:
            for (int y = 0; y < 240; y++)
            {
                for (int x = 0; x < 8; x++)
                {
                    buffer[(y << 8) + x] = 0;
                }
            }
        }

        if (clipToTvSize)
        {
            // Clip right 8-pixels column too:
            for (int y = 0; y < 240; y++)
            {
                for (int x = 0; x < 8; x++)
                {
                    buffer[(y << 8) + 255 - x] = 0;
                }
            }
        }

        // Clip top and bottom 8 pixels:
        if (clipToTvSize)
        {
            for (int y = 0; y < 8; y++)
            {
                for (int x = 0; x < 256; x++)
                {
                    buffer[(y << 8) + x] = 0;
                    buffer[((239 - y) << 8) + x] = 0;
                }
            }
        }

        // Show sound buffer:
        //if (showSoundBuffer && nes.Papu.Line != null)
        //{

        //    bufferSize = nes.Papu.Line.BufferSize;
        //    available = nes.getPapu().getLine().available();
        //    scale = bufferSize / 256;

        //    for (int y = 0; y < 4; y++)
        //    {
        //        scanlineChanged[y] = true;
        //        for (int x = 0; x < 256; x++)
        //        {
        //            if (x >= (available / scale))
        //            {
        //                buffer[y * 256 + x] = 0xFFFFFF;
        //            }
        //            else
        //            {
        //                buffer[y * 256 + x] = 0;
        //            }
        //        }
        //    }
        //}

    }

    public void updateControlReg1(int value)
    {

        triggerRendering();

        f_nmiOnVblank = (value >> 7) & 1;
        f_spriteSize = (value >> 5) & 1;
        f_bgPatternTable = (value >> 4) & 1;
        f_spPatternTable = (value >> 3) & 1;
        f_addrInc = (value >> 2) & 1;
        f_nTblAddress = value & 3;

        regV = (value >> 1) & 1;
        regH = value & 1;
        regS = (value >> 4) & 1;

    }



    public void updateControlReg2(int value)
    {

        triggerRendering();

        f_color = (value >> 5) & 7;
        f_spVisibility = (value >> 4) & 1;
        f_bgVisibility = (value >> 3) & 1;
        f_spClipping = (value >> 2) & 1;
        f_bgClipping = (value >> 1) & 1;
        f_dispType = value & 1;

        if (f_dispType == 0)
        {
            nes.palTable.setEmphasis(f_color);
        }
        updatePalettes();

    }

    public void setStatusFlag(int flag, bool value)
    {

        int n = 1 << flag;
        int memValue = nes.CpuMemory.load(0x2002);
        memValue = ((memValue & (255 - n)) | (value ? n : 0));
        nes.CpuMemory.write(0x2002, (short)memValue);

    }


    // CPU Register $2002:
    // Read the Status Register.
    public short readStatusRegister()
    {

        tmp = nes.CpuMemory.load(0x2002);

        // Reset scroll & VRAM Address toggle:
        firstWrite = true;

        // Clear VBlank flag:
        setStatusFlag(STATUS_VBLANK, false);

        // Fetch status data:
        return tmp;

    }


    // CPU Register $2003:
    // Write the SPR-RAM address that is used for sramWrite (Register 0x2004 in CPU memory map)
    public void writeSRAMAddress(short address)
    {
        sramAddress = address;
    }


    // CPU Register $2004 (R):
    // Read from SPR-RAM (Sprite RAM).
    // The address should be set first.
    public short sramLoad()
    {
        short tmp = sprMem.load(sramAddress);
        /*sramAddress++; // Increment address
        sramAddress%=0x100;*/
        return tmp;
    }


    // CPU Register $2004 (W):
    // Write to SPR-RAM (Sprite RAM).
    // The address should be set first.
    public void sramWrite(short value)
    {
        sprMem.write(sramAddress, value);
        spriteRamWriteUpdate(sramAddress, value);
        sramAddress++; // Increment address
        sramAddress %= 0x100;
    }


    // CPU Register $2005:
    // Write to scroll registers.
    // The first write is the vertical offset, the second is the
    // horizontal offset:
    public void scrollWrite(short value)
    {

        triggerRendering();
        if (firstWrite)
        {

            // First write, horizontal scroll:
            regHT = (value >> 3) & 31;
            regFH = value & 7;

        }
        else
        {

            // Second write, vertical scroll:
            regFV = value & 7;
            regVT = (value >> 3) & 31;

        }
        firstWrite = !firstWrite;

    }



    // CPU Register $2006:
    // Sets the adress used when reading/writing from/to VRAM.
    // The first write sets the high byte, the second the low byte.
    public void writeVRAMAddress(int address)
    {

        if (firstWrite)
        {

            regFV = (address >> 4) & 3;
            regV = (address >> 3) & 1;
            regH = (address >> 2) & 1;
            regVT = (regVT & 7) | ((address & 3) << 3);

        }
        else
        {

            triggerRendering();

            regVT = (regVT & 24) | ((address >> 5) & 7);
            regHT = address & 31;

            cntFV = regFV;
            cntV = regV;
            cntH = regH;
            cntVT = regVT;
            cntHT = regHT;

            checkSprite0(scanline - vblankAdd + 1 - 21);

        }

        firstWrite = !firstWrite;

        // Invoke mapper latch:
        cntsToAddress();
        if (vramAddress < 0x2000)
        {
            nes.memMapper.latchAccess(vramAddress);
        }

    }



    // CPU Register $2007(R):
    // Read from PPU memory. The address should be set first.
    public short vramLoad()
    {

        cntsToAddress();
        regsToAddress();

        // If address is in range 0x0000-0x3EFF, return buffered values:
        short tmp;
        if (vramAddress <= 0x3EFF)
        {

            tmp = vramBufferedReadValue;

            // Update buffered value:
            if (vramAddress < 0x2000)
            {
                vramBufferedReadValue = ppuMem.load(vramAddress);
            }
            else
            {
                vramBufferedReadValue = mirroredLoad(vramAddress);
            }

            // Mapper latch access:
            if (vramAddress < 0x2000)
            {
                nes.memMapper.latchAccess(vramAddress);
            }

            // Increment by either 1 or 32, depending on d2 of Control Register 1:
            vramAddress += (f_addrInc == 1 ? 32 : 1);

            cntsFromAddress();
            regsFromAddress();
            return tmp; // Return the previous buffered value.

        }

        // No buffering in this mem range. Read normally.
        tmp = mirroredLoad(vramAddress);

        // Increment by either 1 or 32, depending on d2 of Control Register 1:
        vramAddress += (f_addrInc == 1 ? 32 : 1);

        cntsFromAddress();
        regsFromAddress();

        return tmp;

    }



    // CPU Register $2007(W):
    // Write to PPU memory. The address should be set first.
    public void vramWrite(short value)
    {

        triggerRendering();
        cntsToAddress();
        regsToAddress();

        if (vramAddress >= 0x2000)
        {
            // Mirroring is used.
            mirroredWrite(vramAddress, value);
        }
        else
        {

            // Write normally.
            writeMem(vramAddress, value);

            // Invoke mapper latch:
            nes.memMapper.latchAccess(vramAddress);

        }

        // Increment by either 1 or 32, depending on d2 of Control Register 1:
        vramAddress += (f_addrInc == 1 ? 32 : 1);
        regsFromAddress();
        cntsFromAddress();

    }



    // CPU Register $4014:
    // Write 256 bytes of main memory
    // into Sprite RAM.
    public void sramDMA(short value)
    {

        Memory cpuMem = nes.CpuMemory;
        int baseAddress = value * 0x100;
        short data;
        for (int i = sramAddress; i < 256; i++)
        {
            data = cpuMem.load(baseAddress + i);
            sprMem.write(i, data);
            spriteRamWriteUpdate(i, data);
        }

        nes.Cpu.haltCycles(513);

    }

    // Updates the scroll registers from a new VRAM address.
    private void regsFromAddress()
    {

        address = (vramTmpAddress >> 8) & 0xFF;
        regFV = (address >> 4) & 7;
        regV = (address >> 3) & 1;
        regH = (address >> 2) & 1;
        regVT = (regVT & 7) | ((address & 3) << 3);

        address = vramTmpAddress & 0xFF;
        regVT = (regVT & 24) | ((address >> 5) & 7);
        regHT = address & 31;



    }

    // Updates the scroll registers from a new VRAM address.
    private void cntsFromAddress()
    {

        address = (vramAddress >> 8) & 0xFF;
        cntFV = (address >> 4) & 3;
        cntV = (address >> 3) & 1;
        cntH = (address >> 2) & 1;
        cntVT = (cntVT & 7) | ((address & 3) << 3);

        address = vramAddress & 0xFF;
        cntVT = (cntVT & 24) | ((address >> 5) & 7);
        cntHT = address & 31;

    }

    private void regsToAddress()
    {

        b1 = (regFV & 7) << 4;
        b1 |= (regV & 1) << 3;
        b1 |= (regH & 1) << 2;
        b1 |= (regVT >> 3) & 3;

        b2 = (regVT & 7) << 5;
        b2 |= regHT & 31;

        vramTmpAddress = ((b1 << 8) | b2) & 0x7FFF;

    }

    private void cntsToAddress()
    {

        b1 = (cntFV & 7) << 4;
        b1 |= (cntV & 1) << 3;
        b1 |= (cntH & 1) << 2;
        b1 |= (cntVT >> 3) & 3;

        b2 = (cntVT & 7) << 5;
        b2 |= cntHT & 31;

        vramAddress = ((b1 << 8) | b2) & 0x7FFF;

    }

    private void incTileCounter(int count)
    {

        for (i = count; i != 0; i--)
        {
            cntHT++;
            if (cntHT == 32)
            {
                cntHT = 0;
                cntVT++;
                if (cntVT >= 30)
                {
                    cntH++;
                    if (cntH == 2)
                    {
                        cntH = 0;
                        cntV++;
                        if (cntV == 2)
                        {
                            cntV = 0;
                            cntFV++;
                            cntFV &= 0x7;
                        }
                    }
                }
            }
        }

    }

    // Reads from memory, taking into account
    // mirroring/mapping of address ranges.
    private short mirroredLoad(int address)
    {

        return ppuMem.load(vramMirrorTable[address]);

    }



    // Writes to memory, taking into account
    // mirroring/mapping of address ranges.
    private void mirroredWrite(int address, short value)
    {

        if (address >= 0x3f00 && address < 0x3f20)
        {

            // Palette write mirroring.

            if (address == 0x3F00 || address == 0x3F10)
            {

                writeMem(0x3F00, value);
                writeMem(0x3F10, value);

            }
            else if (address == 0x3F04 || address == 0x3F14)
            {

                writeMem(0x3F04, value);
                writeMem(0x3F14, value);

            }
            else if (address == 0x3F08 || address == 0x3F18)
            {

                writeMem(0x3F08, value);
                writeMem(0x3F18, value);

            }
            else if (address == 0x3F0C || address == 0x3F1C)
            {

                writeMem(0x3F0C, value);
                writeMem(0x3F1C, value);

            }
            else
            {

                writeMem(address, value);

            }

        }
        else
        {

            // Use lookup table for mirrored address:
            if (address < vramMirrorTable.Length)
            {
                writeMem(vramMirrorTable[address], value);
            }
            else
            {
                if (Globals.debug)
                {
                    //System.out.println("Invalid VRAM address: "+Misc.hex16(address));
                    nes.Cpu.Crashed=true;
                }
            }

        }

    }



    public void triggerRendering()
    {

        if (scanline - vblankAdd >= 21 && scanline - vblankAdd <= 260)
        {

            // Render sprites, and combine:
            renderFramePartially(buffer, lastRenderedScanline + 1, scanline - vblankAdd - 21 - lastRenderedScanline);

            // Set last rendered scanline:
            lastRenderedScanline = scanline - vblankAdd - 21;

        }

    }

    private void renderFramePartially(int[] buffer, int startScan, int scanCount)
    {

        if (f_spVisibility == 1 && !Globals.disableSprites)
        {
            renderSpritesPartially(startScan, scanCount, true);
        }

        if (f_bgVisibility == 1)
        {
            si = startScan << 8;
            ei = (startScan + scanCount) << 8;
            if (ei > 0xF000) ei = 0xF000;
            for (destIndex = si; destIndex < ei; destIndex++)
            {
                if (pixrendered[destIndex] > 0xFF)
                {
                    buffer[destIndex] = bgbuffer[destIndex];
                }
            }
        }

        if (f_spVisibility == 1 && !Globals.disableSprites)
        {
            renderSpritesPartially(startScan, scanCount, false);
        }

        BufferView screen = nes.Gui.ScreenView;
        //if (screen.scalingEnabled() && !screen.useHWScaling() && !requestRenderAll)
        if (true)
        {

            // Check which scanlines have changed, to try to
            // speed up scaling:
            int j, jmax;
            if (startScan + scanCount > 240) scanCount = 240 - startScan;
            for (int i = startScan; i < startScan + scanCount; i++)
            {
                scanlineChanged[i] = false;
                si = i << 8;
                jmax = si + 256;
                for (j = si; j < jmax; j++)
                {
                    if (buffer[j] != oldFrame[j])
                    {
                        scanlineChanged[i] = true;
                        break;
                    }
                    oldFrame[j] = buffer[j];
                }
                Array.Copy(buffer, j, oldFrame, j, jmax - j);
            }

        }

        validTileData = false;

    }

    private void renderBgScanline(int[] buffer, int scan)
    {

        baseTile = (regS == 0 ? 0 : 256);
        destIndex = (scan << 8) - regFH;
        curNt = ntable1[cntV + cntV + cntH];

        cntHT = regHT;
        cntH = regH;
        curNt = ntable1[cntV + cntV + cntH];

        if (scan < 240 && (scan - cntFV) >= 0)
        {

            tscanoffset = cntFV << 3;
            y = scan - cntFV;
            for (tile = 0; tile < 32; tile++)
            {

                if (scan >= 0)
                {

                    // Fetch tile & attrib data:
                    if (validTileData)
                    {
                        // Get data from array:
                        t = scantile[tile];
                        tpix = t.pix;
                        att = attrib[tile];
                    }
                    else
                    {
                        // Fetch data:
                        t = ptTile[baseTile + nameTable[curNt].getTileIndex(cntHT, cntVT)];
                        tpix = t.pix;
                        att = nameTable[curNt].getAttrib(cntHT, cntVT);
                        scantile[tile] = t;
                        attrib[tile] = att;
                    }

                    // Render tile scanline:
                    sx = 0;
                    x = (tile << 3) - regFH;
                    if (x > -8)
                    {
                        if (x < 0)
                        {
                            destIndex -= x;
                            sx = -x;
                        }
                        if (t.opaque[cntFV])
                        {
                            for (; sx < 8; sx++)
                            {
                                buffer[destIndex] = imgPalette[tpix[tscanoffset + sx] + att];
                                pixrendered[destIndex] |= 256;
                                destIndex++;
                            }
                        }
                        else
                        {
                            for (; sx < 8; sx++)
                            {
                                col = tpix[tscanoffset + sx];
                                if (col != 0)
                                {
                                    buffer[destIndex] = imgPalette[col + att];
                                    pixrendered[destIndex] |= 256;
                                }
                                destIndex++;
                            }
                        }
                    }

                }

                // Increase Horizontal Tile Counter:
                cntHT++;
                if (cntHT == 32)
                {
                    cntHT = 0;
                    cntH++;
                    cntH %= 2;
                    curNt = ntable1[(cntV << 1) + cntH];
                }


            }

            // Tile data for one row should now have been fetched,
            // so the data in the array is valid.
            validTileData = true;

        }

        // update vertical scroll:
        cntFV++;
        if (cntFV == 8)
        {
            cntFV = 0;
            cntVT++;
            if (cntVT == 30)
            {
                cntVT = 0;
                cntV++;
                cntV %= 2;
                curNt = ntable1[(cntV << 1) + cntH];
            }
            else if (cntVT == 32)
            {
                cntVT = 0;
            }

            // Invalidate fetched data:
            validTileData = false;

        }

    }

    private void renderSpritesPartially(int startscan, int scancount, bool bgPri)
    {

        buffer = nes.Gui.ScreenView.Buffer;
        if (f_spVisibility == 1)
        {

            int sprT1, sprT2;

            for (int i = 0; i < 64; i++)
            {
                if (bgPriority[i] == bgPri && sprX[i] >= 0 && sprX[i] < 256 && sprY[i] + 8 >= startscan && sprY[i] < startscan + scancount)
                {
                    // Show sprite.
                    if (f_spriteSize == 0)
                    {
                        // 8x8 sprites

                        srcy1 = 0;
                        srcy2 = 8;

                        if (sprY[i] < startscan)
                        {
                            srcy1 = startscan - sprY[i] - 1;
                        }

                        if (sprY[i] + 8 > startscan + scancount)
                        {
                            srcy2 = startscan + scancount - sprY[i] + 1;
                        }

                        if (f_spPatternTable == 0)
                        {
                            ptTile[sprTile[i]].render(0, srcy1, 8, srcy2, sprX[i], sprY[i] + 1, buffer, sprCol[i], sprPalette, horiFlip[i], vertFlip[i], i, pixrendered);
                        }
                        else
                        {
                            ptTile[sprTile[i] + 256].render(0, srcy1, 8, srcy2, sprX[i], sprY[i] + 1, buffer, sprCol[i], sprPalette, horiFlip[i], vertFlip[i], i, pixrendered);
                        }
                    }
                    else
                    {
                        // 8x16 sprites
                        int top = sprTile[i];
                        if ((top & 1) != 0)
                        {
                            top = sprTile[i] - 1 + 256;
                        }

                        srcy1 = 0;
                        srcy2 = 8;

                        if (sprY[i] < startscan)
                        {
                            srcy1 = startscan - sprY[i] - 1;
                        }

                        if (sprY[i] + 8 > startscan + scancount)
                        {
                            srcy2 = startscan + scancount - sprY[i];
                        }

                        ptTile[top + (vertFlip[i] ? 1 : 0)].render(0, srcy1, 8, srcy2, sprX[i], sprY[i] + 1, buffer, sprCol[i], sprPalette, horiFlip[i], vertFlip[i], i, pixrendered);

                        srcy1 = 0;
                        srcy2 = 8;

                        if (sprY[i] + 8 < startscan)
                        {
                            srcy1 = startscan - (sprY[i] + 8 + 1);
                        }

                        if (sprY[i] + 16 > startscan + scancount)
                        {
                            srcy2 = startscan + scancount - (sprY[i] + 8);
                        }

                        ptTile[top + (vertFlip[i] ? 0 : 1)].render(0, srcy1, 8, srcy2, sprX[i], sprY[i] + 1 + 8, buffer, sprCol[i], sprPalette, horiFlip[i], vertFlip[i], i, pixrendered);

                    }
                }
            }
        }

    }

    private bool checkSprite0(int scan)
    {

        spr0HitX = -1;
        spr0HitY = -1;

        int toffset;
        int tIndexAdd = (f_spPatternTable == 0 ? 0 : 256);
        int x, y;
        int bufferIndex;
        int col;
        bool bgPri;
        Tile t;

        x = sprX[0];
        y = sprY[0] + 1;


        if (f_spriteSize == 0)
        {

            // 8x8 sprites.

            // Check range:
            if (y <= scan && y + 8 > scan && x >= -7 && x < 256)
            {

                // Sprite is in range.
                // Draw scanline:
                t = ptTile[sprTile[0] + tIndexAdd];
                col = sprCol[0];
                bgPri = bgPriority[0];

                if (vertFlip[0])
                {
                    toffset = 7 - (scan - y);
                }
                else
                {
                    toffset = scan - y;
                }
                toffset *= 8;

                bufferIndex = scan * 256 + x;
                if (horiFlip[0])
                {
                    for (int i = 7; i >= 0; i--)
                    {
                        if (x >= 0 && x < 256)
                        {
                            if (bufferIndex >= 0 && bufferIndex < 61440 && pixrendered[bufferIndex] != 0)
                            {
                                if (t.pix[toffset + i] != 0)
                                {
                                    spr0HitX = bufferIndex % 256;
                                    spr0HitY = scan;
                                    return true;
                                }
                            }
                        }
                        x++;
                        bufferIndex++;
                    }

                }
                else
                {

                    for (int i = 0; i < 8; i++)
                    {
                        if (x >= 0 && x < 256)
                        {
                            if (bufferIndex >= 0 && bufferIndex < 61440 && pixrendered[bufferIndex] != 0)
                            {
                                if (t.pix[toffset + i] != 0)
                                {
                                    spr0HitX = bufferIndex % 256;
                                    spr0HitY = scan;
                                    return true;
                                }
                            }
                        }
                        x++;
                        bufferIndex++;
                    }

                }

            }


        }
        else
        {

            // 8x16 sprites:

            // Check range:
            if (y <= scan && y + 16 > scan && x >= -7 && x < 256)
            {

                // Sprite is in range.
                // Draw scanline:

                if (vertFlip[0])
                {
                    toffset = 15 - (scan - y);
                }
                else
                {
                    toffset = scan - y;
                }

                if (toffset < 8)
                {
                    // first half of sprite.
                    t = ptTile[sprTile[0] + (vertFlip[0] ? 1 : 0) + ((sprTile[0] & 1) != 0 ? 255 : 0)];
                }
                else
                {
                    // second half of sprite.
                    t = ptTile[sprTile[0] + (vertFlip[0] ? 0 : 1) + ((sprTile[0] & 1) != 0 ? 255 : 0)];
                    if (vertFlip[0])
                    {
                        toffset = 15 - toffset;
                    }
                    else
                    {
                        toffset -= 8;
                    }
                }
                toffset *= 8;
                col = sprCol[0];
                bgPri = bgPriority[0];

                bufferIndex = scan * 256 + x;
                if (horiFlip[0])
                {

                    for (int i = 7; i >= 0; i--)
                    {
                        if (x >= 0 && x < 256)
                        {
                            if (bufferIndex >= 0 && bufferIndex < 61440 && pixrendered[bufferIndex] != 0)
                            {
                                if (t.pix[toffset + i] != 0)
                                {
                                    spr0HitX = bufferIndex % 256;
                                    spr0HitY = scan;
                                    return true;
                                }
                            }
                        }
                        x++;
                        bufferIndex++;
                    }

                }
                else
                {

                    for (int i = 0; i < 8; i++)
                    {
                        if (x >= 0 && x < 256)
                        {
                            if (bufferIndex >= 0 && bufferIndex < 61440 && pixrendered[bufferIndex] != 0)
                            {
                                if (t.pix[toffset + i] != 0)
                                {
                                    spr0HitX = bufferIndex % 256;
                                    spr0HitY = scan;
                                    return true;
                                }
                            }
                        }
                        x++;
                        bufferIndex++;
                    }

                }

            }

        }

        return false;

    }

    // Renders the contents of the
    // pattern table into an image.
    public void renderPattern()
    {

        BufferView scr = nes.Gui.PatternView;
        int[] buffer = scr.Buffer;

        int tIndex = 0;
        for (int j = 0; j < 2; j++)
        {
            for (int y = 0; y < 16; y++)
            {
                for (int x = 0; x < 16; x++)
                {
                    ptTile[tIndex].renderSimple(j * 128 + x * 8, y * 8, buffer, 0, sprPalette);
                    tIndex++;
                }
            }
        }
        nes.Gui.PatternView.imageReady(false);

    }


    public void renderNameTables()
    {

        int[] buffer = nes.Gui.NameTableView.Buffer;
        if (f_bgPatternTable == 0)
        {
            baseTile = 0;
        }
        else
        {
            baseTile = 256;
        }

        int ntx_max = 2;
        int nty_max = 2;

        if (currentMirroring == ROM.HORIZONTAL_MIRRORING)
        {
            ntx_max = 1;
        }
        else if (currentMirroring == ROM.VERTICAL_MIRRORING)
        {
            nty_max = 1;
        }

        for (int nty = 0; nty < nty_max; nty++)
        {
            for (int ntx = 0; ntx < ntx_max; ntx++)
            {

                int nt = ntable1[nty * 2 + ntx];
                int x = ntx * 128;
                int y = nty * 120;

                // Render nametable:
                for (int ty = 0; ty < 30; ty++)
                {
                    for (int tx = 0; tx < 32; tx++)
                    {
                        //ptTile[baseTile+nameTable[nt].getTileIndex(tx,ty)].render(0,0,4,4,x+tx*4,y+ty*4,buffer,nameTable[nt].getAttrib(tx,ty),imgPalette,false,false,0,dummyPixPriTable);
                        ptTile[baseTile + nameTable[nt].getTileIndex(tx, ty)].renderSmall(x + tx * 4, y + ty * 4, buffer, nameTable[nt].getAttrib(tx, ty), imgPalette);
                    }
                }

            }
        }

        if (currentMirroring == ROM.HORIZONTAL_MIRRORING)
        {
            // double horizontally:
            for (int y = 0; y < 240; y++)
            {
                for (int x = 0; x < 128; x++)
                {
                    buffer[(y << 8) + 128 + x] = buffer[(y << 8) + x];
                }
            }
        }
        else if (currentMirroring == ROM.VERTICAL_MIRRORING)
        {
            // double vertically:
            for (int y = 0; y < 120; y++)
            {
                for (int x = 0; x < 256; x++)
                {
                    buffer[(y << 8) + 0x7800 + x] = buffer[(y << 8) + x];
                }
            }
        }

        nes.Gui.NameTableView.imageReady(false);

    }

    private void renderPalettes()
    {

        int[] buffer = nes.Gui.ImgPalView.Buffer;
        for (int i = 0; i < 16; i++)
        {
            for (int y = 0; y < 16; y++)
            {
                for (int x = 0; x < 16; x++)
                {
                    buffer[y * 256 + i * 16 + x] = imgPalette[i];
                }
            }
        }

        buffer = nes.Gui.SprPalView.Buffer;
        for (int i = 0; i < 16; i++)
        {
            for (int y = 0; y < 16; y++)
            {
                for (int x = 0; x < 16; x++)
                {
                    buffer[y * 256 + i * 16 + x] = sprPalette[i];
                }
            }
        }

        nes.Gui.ImgPalView.imageReady(false);
        nes.Gui.SprPalView.imageReady(false);

    }


    // This will write to PPU memory, and
    // update internally buffered data
    // appropriately.
    private void writeMem(int address, short value)
    {

        ppuMem.write(address, value);

        // Update internally buffered data:
        if (address < 0x2000)
        {

            ppuMem.write(address, value);
            patternWrite(address, value);

        }
        else if (address >= 0x2000 && address < 0x23c0)
        {

            nameTableWrite(ntable1[0], address - 0x2000, value);

        }
        else if (address >= 0x23c0 && address < 0x2400)
        {

            attribTableWrite(ntable1[0], address - 0x23c0, value);

        }
        else if (address >= 0x2400 && address < 0x27c0)
        {

            nameTableWrite(ntable1[1], address - 0x2400, value);

        }
        else if (address >= 0x27c0 && address < 0x2800)
        {

            attribTableWrite(ntable1[1], address - 0x27c0, value);

        }
        else if (address >= 0x2800 && address < 0x2bc0)
        {

            nameTableWrite(ntable1[2], address - 0x2800, value);

        }
        else if (address >= 0x2bc0 && address < 0x2c00)
        {

            attribTableWrite(ntable1[2], address - 0x2bc0, value);

        }
        else if (address >= 0x2c00 && address < 0x2fc0)
        {

            nameTableWrite(ntable1[3], address - 0x2c00, value);

        }
        else if (address >= 0x2fc0 && address < 0x3000)
        {

            attribTableWrite(ntable1[3], address - 0x2fc0, value);

        }
        else if (address >= 0x3f00 && address < 0x3f20)
        {

            updatePalettes();

        }

    }



    // Reads data from $3f00 to $f20
    // into the two buffered palettes.
    public void updatePalettes()
    {

        for (int i = 0; i < 16; i++)
        {
            if (f_dispType == 0)
            {
                imgPalette[i] = nes.palTable.getEntry(ppuMem.load(0x3f00 + i) & 63);
            }
            else
            {
                imgPalette[i] = nes.palTable.getEntry(ppuMem.load(0x3f00 + i) & 32);
            }
        }
        for (int i = 0; i < 16; i++)
        {
            if (f_dispType == 0)
            {
                sprPalette[i] = nes.palTable.getEntry(ppuMem.load(0x3f10 + i) & 63);
            }
            else
            {
                sprPalette[i] = nes.palTable.getEntry(ppuMem.load(0x3f10 + i) & 32);
            }
        }

        //renderPalettes();

    }


    // Updates the internal pattern
    // table buffers with this new byte.
    public void patternWrite(int address, short value)
    {
        int tileIndex = address / 16;
        int leftOver = address % 16;
        if (leftOver < 8)
        {
            ptTile[tileIndex].setScanline(leftOver, value, ppuMem.load(address + 8));
        }
        else
        {
            ptTile[tileIndex].setScanline(leftOver - 8, ppuMem.load(address - 8), value);
        }
    }

    public void patternWrite(int address, short[] value, int offset, int length)
    {

        int tileIndex;
        int leftOver;

        for (int i = 0; i < length; i++)
        {

            tileIndex = (address + i) >> 4;
            leftOver = (address + i) % 16;

            if (leftOver < 8)
            {
                ptTile[tileIndex].setScanline(leftOver, value[offset + i], ppuMem.load(address + 8 + i));
            }
            else
            {
                ptTile[tileIndex].setScanline(leftOver - 8, ppuMem.load(address - 8 + i), value[offset + i]);
            }

        }

    }

    public void invalidateFrameCache()
    {

        // Clear the no-update scanline buffer:
        for (int i = 0; i < 240; i++) scanlineChanged[i] = true;
        for (int index = 0; index < oldFrame.Length; index++)
        {
            oldFrame[index] = -1;
        }
        requestRenderAll = true;

    }

    // Updates the internal name table buffers
    // with this new byte.
    public void nameTableWrite(int index, int address, short value)
    {
        nameTable[index].writeTileIndex(address, value);

        // Update Sprite #0 hit:
        //updateSpr0Hit();
        checkSprite0(scanline + 1 - vblankAdd - 21);

    }



    // Updates the internal pattern
    // table buffers with this new attribute
    // table byte.
    public void attribTableWrite(int index, int address, short value)
    {
        nameTable[index].writeAttrib(address, value);
    }



    // Updates the internally buffered sprite
    // data with this new byte of info.
    public void spriteRamWriteUpdate(int address, short value)
    {

        int tIndex = address / 4;

        if (tIndex == 0)
        {
            //updateSpr0Hit();
            checkSprite0(scanline + 1 - vblankAdd - 21);
        }

        if (address % 4 == 0)
        {

            // Y coordinate
            sprY[tIndex] = value;

        }
        else if (address % 4 == 1)
        {

            // Tile index
            sprTile[tIndex] = value;

        }
        else if (address % 4 == 2)
        {

            // Attributes
            vertFlip[tIndex] = ((value & 0x80) != 0);
            horiFlip[tIndex] = ((value & 0x40) != 0);
            bgPriority[tIndex] = ((value & 0x20) != 0);
            sprCol[tIndex] = (value & 3) << 2;

        }
        else if (address % 4 == 3)
        {

            // X coordinate
            sprX[tIndex] = value;

        }

    }

    public void doNMI()
    {

        // Set VBlank flag:
        setStatusFlag(STATUS_VBLANK, true);
        //nes.getCpu().doNonMaskableInterrupt();
        nes.Cpu.requestIrq(CPU.IRQ_NMI);

    }

    public int statusRegsToInt()
    {

        int ret = 0;
        ret = (f_nmiOnVblank) |
                (f_spriteSize << 1) |
                (f_bgPatternTable << 2) |
                (f_spPatternTable << 3) |
                (f_addrInc << 4) |
                (f_nTblAddress << 5) |

                (f_color << 6) |
                (f_spVisibility << 7) |
                (f_bgVisibility << 8) |
                (f_spClipping << 9) |
                (f_bgClipping << 10) |
                (f_dispType << 11);

        return ret;

    }

    public void statusRegsFromInt(int n)
    {

        f_nmiOnVblank = (n) & 0x1;
        f_spriteSize = (n >> 1) & 0x1;
        f_bgPatternTable = (n >> 2) & 0x1;
        f_spPatternTable = (n >> 3) & 0x1;
        f_addrInc = (n >> 4) & 0x1;
        f_nTblAddress = (n >> 5) & 0x1;

        f_color = (n >> 6) & 0x1;
        f_spVisibility = (n >> 7) & 0x1;
        f_bgVisibility = (n >> 8) & 0x1;
        f_spClipping = (n >> 9) & 0x1;
        f_bgClipping = (n >> 10) & 0x1;
        f_dispType = (n >> 11) & 0x1;

    }

    public void stateLoad(ByteBuffer buf)
    {

        // Check version:
        if (buf.readByte() == 1)
        {

            // Counters:
            cntFV = buf.readInt();
            cntV = buf.readInt();
            cntH = buf.readInt();
            cntVT = buf.readInt();
            cntHT = buf.readInt();


            // Registers:
            regFV = buf.readInt();
            regV = buf.readInt();
            regH = buf.readInt();
            regVT = buf.readInt();
            regHT = buf.readInt();
            regFH = buf.readInt();
            regS = buf.readInt();


            // VRAM address:
            vramAddress = buf.readInt();
            vramTmpAddress = buf.readInt();


            // Control/Status registers:
            statusRegsFromInt(buf.readInt());


            // VRAM I/O:
            vramBufferedReadValue = (short)buf.readInt();
            firstWrite = buf.readBoolean();
            //System.out.println("firstWrite: "+firstWrite);


            // Mirroring:
            //currentMirroring = -1;
            //setMirroring(buf.readInt());
            for (int i = 0; i < vramMirrorTable.Length; i++)
            {
                vramMirrorTable[i] = buf.readInt();
            }


            // SPR-RAM I/O:
            sramAddress = (short)buf.readInt();

            // Rendering progression:
            curX = buf.readInt();
            scanline = buf.readInt();
            lastRenderedScanline = buf.readInt();


            // Misc:
            requestEndFrame = buf.readBoolean();
            nmiOk = buf.readBoolean();
            dummyCycleToggle = buf.readBoolean();
            nmiCounter = buf.readInt();
            tmp = (short)buf.readInt();


            // Stuff used during rendering:
            for (int i = 0; i < bgbuffer.Length; i++)
            {
                bgbuffer[i] = buf.readByte();
            }
            for (int i = 0; i < pixrendered.Length; i++)
            {
                pixrendered[i] = buf.readByte();
            }

            // Name tables:
            for (int i = 0; i < 4; i++)
            {
                ntable1[i] = buf.readByte();
                nameTable[i].stateLoad(buf);
            }

            // Pattern data:
            for (int i = 0; i < ptTile.Length; i++)
            {
                ptTile[i].stateLoad(buf);
            }

            // Update internally stored stuff from VRAM memory:
            /*short[] mem = ppuMem.mem;

            // Palettes:
            for(int i=0x3f00;i<0x3f20;i++){
                writeMem(i,mem[i]);
            }
            */
            // Sprite data:
            short[] sprmem = nes.SprMemory.mem;
            for (int i = 0; i < sprmem.Length; i++)
            {
                spriteRamWriteUpdate(i, sprmem[i]);
            }

        }

    }

    public void stateSave(ByteBuffer buf)
    {


        // Version:
        buf.putByte((short)1);


        // Counters:
        buf.putInt(cntFV);
        buf.putInt(cntV);
        buf.putInt(cntH);
        buf.putInt(cntVT);
        buf.putInt(cntHT);


        // Registers:
        buf.putInt(regFV);
        buf.putInt(regV);
        buf.putInt(regH);
        buf.putInt(regVT);
        buf.putInt(regHT);
        buf.putInt(regFH);
        buf.putInt(regS);


        // VRAM address:
        buf.putInt(vramAddress);
        buf.putInt(vramTmpAddress);


        // Control/Status registers:
        buf.putInt(statusRegsToInt());


        // VRAM I/O:
        buf.putInt(vramBufferedReadValue);
        //System.out.println("firstWrite: "+firstWrite);
        buf.putBoolean(firstWrite);

        // Mirroring:
        //buf.putInt(currentMirroring);
        for (int i = 0; i < vramMirrorTable.Length; i++)
        {
            buf.putInt(vramMirrorTable[i]);
        }


        // SPR-RAM I/O:
        buf.putInt(sramAddress);


        // Rendering progression:
        buf.putInt(curX);
        buf.putInt(scanline);
        buf.putInt(lastRenderedScanline);


        // Misc:
        buf.putBoolean(requestEndFrame);
        buf.putBoolean(nmiOk);
        buf.putBoolean(dummyCycleToggle);
        buf.putInt(nmiCounter);
        buf.putInt(tmp);


        // Stuff used during rendering:
        for (int i = 0; i < bgbuffer.Length; i++)
        {
            buf.putByte((short)bgbuffer[i]);
        }
        for (int i = 0; i < pixrendered.Length; i++)
        {
            buf.putByte((short)pixrendered[i]);
        }

        // Name tables:
        for (int i = 0; i < 4; i++)
        {
            buf.putByte((short)ntable1[i]);
            nameTable[i].stateSave(buf);
        }

        // Pattern data:
        for (int i = 0; i < ptTile.Length; i++)
        {
            ptTile[i].stateSave(buf);
        }

    }

    // Reset PPU:
    public void reset()
    {

        ppuMem.reset();
        sprMem.reset();

        vramBufferedReadValue = 0;
        sramAddress = 0;
        curX = 0;
        scanline = 0;
        lastRenderedScanline = 0;
        spr0HitX = 0;
        spr0HitY = 0;
        mapperIrqCounter = 0;

        currentMirroring = -1;

        firstWrite = true;
        requestEndFrame = false;
        nmiOk = false;
        hitSpr0 = false;
        dummyCycleToggle = false;
        validTileData = false;
        nmiCounter = 0;
        tmp = 0;
        att = 0;
        i = 0;

        // Control Flags Register 1:
        f_nmiOnVblank = 0;    // NMI on VBlank. 0=disable, 1=enable
        f_spriteSize = 0;     // Sprite size. 0=8x8, 1=8x16
        f_bgPatternTable = 0; // Background Pattern Table address. 0=0x0000,1=0x1000
        f_spPatternTable = 0; // Sprite Pattern Table address. 0=0x0000,1=0x1000
        f_addrInc = 0;        // PPU Address Increment. 0=1,1=32
        f_nTblAddress = 0;    // Name Table Address. 0=0x2000,1=0x2400,2=0x2800,3=0x2C00

        // Control Flags Register 2:
        f_color = 0;	   	  // Background color. 0=black, 1=blue, 2=green, 4=red
        f_spVisibility = 0;   // Sprite visibility. 0=not displayed,1=displayed
        f_bgVisibility = 0;   // Background visibility. 0=Not Displayed,1=displayed
        f_spClipping = 0;     // Sprite clipping. 0=Sprites invisible in left 8-pixel column,1=No clipping
        f_bgClipping = 0;     // Background clipping. 0=BG invisible in left 8-pixel column, 1=No clipping
        f_dispType = 0;       // Display type. 0=color, 1=monochrome


        // Counters:
        cntFV = 0;
        cntV = 0;
        cntH = 0;
        cntVT = 0;
        cntHT = 0;

        // Registers:
        regFV = 0;
        regV = 0;
        regH = 0;
        regVT = 0;
        regHT = 0;
        regFH = 0;
        regS = 0;

        for (int index = 0; index < scanlineChanged.Length; index++)
        {
            scanlineChanged[index] = true;
        }
        for (int j = 0; j < oldFrame.Length; j++)
        {
            oldFrame[j] = -1;
        }
        // Initialize stuff:
        init();

    }

    public void destroy()
    {

        nes = null;
        ppuMem = null;
        sprMem = null;
        scantile = null;

    }

}


public class PPU2
{
    internal int address;
    internal int att;
    internal int[] attrib = new int[0x20];
    internal int available;
    internal int b1;
    internal int b2;
    internal int baseTile;
    internal int[] bgbuffer = new int[0xf000];
    public bool[] bgPriority;
    internal int[] buffer;
    internal int bufferSize;
    internal bool clipToTvSize = true;
    internal int cntFV;
    internal int cntH;
    internal int cntHT;
    internal int cntV;
    internal int cntVT;
    internal int col;
    internal int curNt;
    internal int currentMirroring = -1;
    public int curX;
    public int cycles;
    internal int destIndex;
    internal bool dummyCycleToggle;
    internal int[] dummyPixPriTable = new int[0xf000];
    internal int ei;
    public int f_addrInc;
    public int f_bgClipping;
    public int f_bgPatternTable;
    public int f_bgVisibility;
    public int f_color;
    public int f_dispType;
    public int f_nmiOnVblank;
    public int f_nTblAddress;
    public int f_spClipping;
    public int f_spPatternTable;
    public int f_spriteSize;
    public int f_spVisibility;
    internal bool firstWrite = true;
    internal bool hitSpr0;
    public bool[] horiFlip;
    internal int i;
    internal int[] imgPalette = new int[0x10];
    public int lastRenderedScanline;
    public int mapperIrqCounter;
    internal NameTable[] nameTable;
    private NES nes;
    internal int nmiCounter;
    internal bool nmiOk;
    internal int[] ntable1 = new int[4];
    internal int[] oldFrame = new int[0xf000];
    internal int[] pixrendered = new int[0xf000];
    private Memory ppuMem;
    public Tile[] ptTile;
    internal int regFH;
    internal int regFV;
    internal int regH;
    internal int regHT;
    internal int regS;
    internal int regV;
    internal int regVT;
    internal bool requestEndFrame;
    internal bool requestRenderAll;
    internal int scale;
    public int scanline;
    internal bool scanlineAlreadyRendered;
    internal bool[] scanlineChanged = new bool[240];
    internal Tile[] scantile = new Tile[0x20];
    internal bool showSoundBuffer;
    internal bool showSpr0Hit;
    internal int si;
    internal int[] spr0dummybuffer = new int[0xf000];
    public int spr0HitX;
    public int spr0HitY;
    public int[] sprCol;
    private Memory sprMem;
    internal int[] sprPalette = new int[0x10];
    public int[] sprTile;
    public int[] sprX;
    public int[] sprY;
    internal short sramAddress;
    internal int srcy1;
    internal int srcy2;
    public int STATUS_SLSPRITECOUNT = 5;
    public int STATUS_SPRITE0HIT = 6;
    public int STATUS_VBLANK = 7;
    public int STATUS_VRAMWRITE = 4;
    internal int sx;
    internal Tile t;
    internal int tile;
    internal short tmp;
    internal int[] tpix;
    internal int tscanoffset;
    internal bool validTileData;
    internal int vblankAdd;
    public bool[] vertFlip;
    internal int vramAddress;
    internal short vramBufferedReadValue;
    internal int[] vramMirrorTable;
    internal int vramTmpAddress;
    internal int x;
    internal int y;

    public PPU2(NES nes)
    {
        this.nes = nes;
    }

    public virtual void attribTableWrite(int index, int address, short value_Renamed)
    {
        this.nameTable[index].writeAttrib(address, value_Renamed);
    }

    private bool checkSprite0(int scan)
    {
        int num;
        int num5;
        Tile tile;
        this.spr0HitX = -1;
        this.spr0HitY = -1;
        int num2 = (this.f_spPatternTable == 0) ? 0 : 0x100;
        int num3 = this.sprX[0];
        int num4 = this.sprY[0] + 1;
        if (this.f_spriteSize == 0)
        {
            if (((num4 <= scan) && ((num4 + 8) > scan)) && ((num3 >= -7) && (num3 < 0x100)))
            {
                tile = this.ptTile[this.sprTile[0] + num2];
                int num1 = this.sprCol[0];
                bool flag1 = this.bgPriority[0];
                if (this.vertFlip[0])
                {
                    num = 7 - (scan - num4);
                }
                else
                {
                    num = scan - num4;
                }
                num *= 8;
                num5 = (scan * 0x100) + num3;
                if (this.horiFlip[0])
                {
                    for (int i = 7; i >= 0; i--)
                    {
                        if ((((num3 >= 0) && (num3 < 0x100)) && ((num5 >= 0) && (num5 < 0xf000))) && ((this.pixrendered[num5] != 0) && (tile.pix[num + i] != 0)))
                        {
                            this.spr0HitX = num5 % 0x100;
                            this.spr0HitY = scan;
                            return true;
                        }
                        num3++;
                        num5++;
                    }
                }
                else
                {
                    for (int j = 0; j < 8; j++)
                    {
                        if ((((num3 >= 0) && (num3 < 0x100)) && ((num5 >= 0) && (num5 < 0xf000))) && ((this.pixrendered[num5] != 0) && (tile.pix[num + j] != 0)))
                        {
                            this.spr0HitX = num5 % 0x100;
                            this.spr0HitY = scan;
                            return true;
                        }
                        num3++;
                        num5++;
                    }
                }
            }
        }
        else if (((num4 <= scan) && ((num4 + 0x10) > scan)) && ((num3 >= -7) && (num3 < 0x100)))
        {
            if (this.vertFlip[0])
            {
                num = 15 - (scan - num4);
            }
            else
            {
                num = scan - num4;
            }
            if (num < 8)
            {
                tile = this.ptTile[(this.sprTile[0] + (this.vertFlip[0] ? 1 : 0)) + (((this.sprTile[0] & 1) != 0) ? 0xff : 0)];
            }
            else
            {
                tile = this.ptTile[(this.sprTile[0] + (this.vertFlip[0] ? 0 : 1)) + (((this.sprTile[0] & 1) != 0) ? 0xff : 0)];
                if (this.vertFlip[0])
                {
                    num = 15 - num;
                }
                else
                {
                    num -= 8;
                }
            }
            num *= 8;
            int num10 = this.sprCol[0];
            bool flag2 = this.bgPriority[0];
            num5 = (scan * 0x100) + num3;
            if (this.horiFlip[0])
            {
                for (int k = 7; k >= 0; k--)
                {
                    if ((((num3 >= 0) && (num3 < 0x100)) && ((num5 >= 0) && (num5 < 0xf000))) && ((this.pixrendered[num5] != 0) && (tile.pix[num + k] != 0)))
                    {
                        this.spr0HitX = num5 % 0x100;
                        this.spr0HitY = scan;
                        return true;
                    }
                    num3++;
                    num5++;
                }
            }
            else
            {
                for (int m = 0; m < 8; m++)
                {
                    if ((((num3 >= 0) && (num3 < 0x100)) && ((num5 >= 0) && (num5 < 0xf000))) && ((this.pixrendered[num5] != 0) && (tile.pix[num + m] != 0)))
                    {
                        this.spr0HitX = num5 % 0x100;
                        this.spr0HitY = scan;
                        return true;
                    }
                    num3++;
                    num5++;
                }
            }
        }
        return false;
    }

    private void cntsFromAddress()
    {
        this.address = (this.vramAddress >> 8) & 0xff;
        this.cntFV = (this.address >> 4) & 3;
        this.cntV = (this.address >> 3) & 1;
        this.cntH = (this.address >> 2) & 1;
        this.cntVT = (this.cntVT & 7) | ((this.address & 3) << 3);
        this.address = this.vramAddress & 0xff;
        this.cntVT = (this.cntVT & 0x18) | ((this.address >> 5) & 7);
        this.cntHT = this.address & 0x1f;
    }

    private void cntsToAddress()
    {
        this.b1 = (this.cntFV & 7) << 4;
        this.b1 |= (this.cntV & 1) << 3;
        this.b1 |= (this.cntH & 1) << 2;
        this.b1 |= (this.cntVT >> 3) & 3;
        this.b2 = (this.cntVT & 7) << 5;
        this.b2 |= this.cntHT & 0x1f;
        this.vramAddress = ((this.b1 << 8) | this.b2) & 0x7fff;
    }

    private void defineMirrorRegion(int fromStart, int toStart, int size)
    {
        for (int i = 0; i < size; i++)
        {
            this.vramMirrorTable[fromStart + i] = toStart + i;
        }
    }

    public virtual void destroy()
    {
        this.nes = null;
        this.ppuMem = null;
        this.sprMem = null;
        this.scantile = null;
    }

    public virtual void doNMI()
    {
        this.setStatusFlag(this.STATUS_VBLANK, true);
        this.nes.Cpu.requestIrq(1);
    }

    public virtual void emulateCycles()
    {
        while (this.cycles > 0)
        {
            if ((((this.scanline - 0x15) == this.spr0HitY) && (this.curX == this.spr0HitX)) && (this.f_spVisibility == 1))
            {
                this.setStatusFlag(this.STATUS_SPRITE0HIT, true);
            }
            if (this.requestEndFrame)
            {
                this.nmiCounter--;
                if (this.nmiCounter == 0)
                {
                    this.requestEndFrame = false;
                    this.startVBlank();
                }
            }
            this.curX++;
            if (this.curX == 0x155)
            {
                this.curX = 0;
                this.endScanline();
            }
            this.cycles--;
        }
    }

    public virtual void endFrame()
    {
        int[] buffer = this.nes.Gui.ScreenView.Buffer;
        if (this.showSpr0Hit)
        {
            if (((this.sprX[0] >= 0) && (this.sprX[0] < 0x100)) && ((this.sprY[0] >= 0) && (this.sprY[0] < 240)))
            {
                for (int i = 0; i < 0x100; i++)
                {
                    buffer[(this.sprY[0] << 8) + i] = 0xff5555;
                }
                for (int j = 0; j < 240; j++)
                {
                    buffer[(j << 8) + this.sprX[0]] = 0xff5555;
                }
            }
            if (((this.spr0HitX >= 0) && (this.spr0HitX < 0x100)) && ((this.spr0HitY >= 0) && (this.spr0HitY < 240)))
            {
                for (int k = 0; k < 0x100; k++)
                {
                    buffer[(this.spr0HitY << 8) + k] = 0x55ff55;
                }
                for (int m = 0; m < 240; m++)
                {
                    buffer[(m << 8) + this.spr0HitX] = 0x55ff55;
                }
            }
        }
        if ((this.clipToTvSize || (this.f_bgClipping == 0)) || (this.f_spClipping == 0))
        {
            for (int n = 0; n < 240; n++)
            {
                for (int num6 = 0; num6 < 8; num6++)
                {
                    buffer[(n << 8) + num6] = 0;
                }
            }
        }
        if (this.clipToTvSize)
        {
            for (int num7 = 0; num7 < 240; num7++)
            {
                for (int num8 = 0; num8 < 8; num8++)
                {
                    buffer[((num7 << 8) + 0xff) - num8] = 0;
                }
            }
        }
        if (this.clipToTvSize)
        {
            for (int num9 = 0; num9 < 8; num9++)
            {
                for (int num10 = 0; num10 < 0x100; num10++)
                {
                    buffer[(num9 << 8) + num10] = 0;
                    buffer[((0xef - num9) << 8) + num10] = 0;
                }
            }
        }
    }

    public virtual void endScanline()
    {
        if (this.scanline >= (0x13 + this.vblankAdd))
        {
            if (this.scanline == (0x13 + this.vblankAdd))
            {
                if (this.dummyCycleToggle)
                {
                    this.curX = 1;
                    this.dummyCycleToggle = !this.dummyCycleToggle;
                }
            }
            else if (this.scanline == (20 + this.vblankAdd))
            {
                this.setStatusFlag(this.STATUS_VBLANK, false);
                this.setStatusFlag(this.STATUS_SPRITE0HIT, false);
                this.hitSpr0 = false;
                this.spr0HitX = -1;
                this.spr0HitY = -1;
                if ((this.f_bgVisibility == 1) || (this.f_spVisibility == 1))
                {
                    this.cntFV = this.regFV;
                    this.cntV = this.regV;
                    this.cntH = this.regH;
                    this.cntVT = this.regVT;
                    this.cntHT = this.regHT;
                    if (this.f_bgVisibility == 1)
                    {
                        this.renderBgScanline(this.buffer, 0);
                    }
                }
                if ((this.f_bgVisibility == 1) && (this.f_spVisibility == 1))
                {
                    this.checkSprite0(0);
                }
                if ((this.f_bgVisibility == 1) || (this.f_spVisibility == 1))
                {
                    this.nes.memMapper.clockIrqCounter();
                }
            }
            else if ((this.scanline >= (0x15 + this.vblankAdd)) && (this.scanline <= 260))
            {
                if (this.f_bgVisibility == 1)
                {
                    if (!this.scanlineAlreadyRendered)
                    {
                        this.cntHT = this.regHT;
                        this.cntH = this.regH;
                        this.renderBgScanline(this.bgbuffer, (this.scanline + 1) - 0x15);
                    }
                    this.scanlineAlreadyRendered = false;
                    if (((((!this.hitSpr0 && (this.f_spVisibility == 1)) && ((this.sprX[0] >= -7) && (this.sprX[0] < 0x100))) && ((this.sprY[0] + 1) <= (((this.scanline - this.vblankAdd) + 1) - 0x15))) && (((this.sprY[0] + 1) + ((this.f_spriteSize == 0) ? 8 : 0x10)) >= (((this.scanline - this.vblankAdd) + 1) - 0x15))) && this.checkSprite0(((this.scanline + this.vblankAdd) + 1) - 0x15))
                    {
                        this.hitSpr0 = true;
                    }
                }
                if ((this.f_bgVisibility == 1) || (this.f_spVisibility == 1))
                {
                    this.nes.memMapper.clockIrqCounter();
                }
            }
            else if (this.scanline == (0x105 + this.vblankAdd))
            {
                this.setStatusFlag(this.STATUS_VBLANK, true);
                this.requestEndFrame = true;
                this.nmiCounter = 9;
                this.scanline = -1;
            }
        }
        this.scanline++;
        this.regsToAddress();
        this.cntsToAddress();
    }

    private void incTileCounter(int count)
    {
        this.i = count;
        while (this.i != 0)
        {
            this.cntHT++;
            if (this.cntHT == 0x20)
            {
                this.cntHT = 0;
                this.cntVT++;
                if (this.cntVT >= 30)
                {
                    this.cntH++;
                    if (this.cntH == 2)
                    {
                        this.cntH = 0;
                        this.cntV++;
                        if (this.cntV == 2)
                        {
                            this.cntV = 0;
                            this.cntFV++;
                            this.cntFV &= 7;
                        }
                    }
                }
            }
            this.i--;
        }
    }

    public virtual void init()
    {
        this.ppuMem = this.nes.PpuMemory;
        this.sprMem = this.nes.SprMemory;
        this.updateControlReg1(0);
        this.updateControlReg2(0);
        this.scanline = 0;
        this.sprX = new int[0x40];
        this.sprY = new int[0x40];
        this.sprTile = new int[0x40];
        this.sprCol = new int[0x40];
        this.vertFlip = new bool[0x40];
        this.horiFlip = new bool[0x40];
        this.bgPriority = new bool[0x40];
        if (this.ptTile == null)
        {
            this.ptTile = new Tile[0x200];
            for (int m = 0; m < 0x200; m++)
            {
                this.ptTile[m] = new Tile();
            }
        }
        this.nameTable = new NameTable[4];
        for (int i = 0; i < 4; i++)
        {
            this.nameTable[i] = new NameTable(0x20, 0x20, "Nt" + i);
        }
        this.vramMirrorTable = new int[0x8000];
        for (int j = 0; j < 0x8000; j++)
        {
            this.vramMirrorTable[j] = j;
        }
        this.lastRenderedScanline = -1;
        this.curX = 0;
        for (int k = 0; k < this.oldFrame.Length; k++)
        {
            this.oldFrame[k] = -1;
        }
    }

    public virtual void invalidateFrameCache()
    {
        for (int i = 0; i < 240; i++)
        {
            this.scanlineChanged[i] = true;
        }
        SupportClass.ArraySupport.Fill(this.oldFrame, -1);
        this.requestRenderAll = true;
    }

    private short mirroredLoad(int address)
    {
        return this.ppuMem.load(this.vramMirrorTable[address]);
    }

    private void mirroredWrite(int address, short value_Renamed)
    {
        if ((address >= 0x3f00) && (address < 0x3f20))
        {
            if ((address == 0x3f00) || (address == 0x3f10))
            {
                this.writeMem(0x3f00, value_Renamed);
                this.writeMem(0x3f10, value_Renamed);
            }
            else if ((address == 0x3f04) || (address == 0x3f14))
            {
                this.writeMem(0x3f04, value_Renamed);
                this.writeMem(0x3f14, value_Renamed);
            }
            else if ((address == 0x3f08) || (address == 0x3f18))
            {
                this.writeMem(0x3f08, value_Renamed);
                this.writeMem(0x3f18, value_Renamed);
            }
            else if ((address == 0x3f0c) || (address == 0x3f1c))
            {
                this.writeMem(0x3f0c, value_Renamed);
                this.writeMem(0x3f1c, value_Renamed);
            }
            else
            {
                this.writeMem(address, value_Renamed);
            }
        }
        else if (address < this.vramMirrorTable.Length)
        {
            this.writeMem(this.vramMirrorTable[address], value_Renamed);
        }
    }

    public virtual void nameTableWrite(int index, int address, short value_Renamed)
    {
        this.nameTable[index].writeTileIndex(address, value_Renamed);
        this.checkSprite0(((this.scanline + 1) - this.vblankAdd) - 0x15);
    }

    public virtual void patternWrite(int address, short value_Renamed)
    {
        int index = address / 0x10;
        int sline = address % 0x10;
        if (sline < 8)
        {
            this.ptTile[index].setScanline(sline, value_Renamed, this.ppuMem.load(address + 8));
        }
        else
        {
            this.ptTile[index].setScanline(sline - 8, this.ppuMem.load(address - 8), value_Renamed);
        }
    }

    public virtual void patternWrite(int address, short[] value_Renamed, int offset, int length)
    {
        for (int i = 0; i < length; i++)
        {
            int index = (address + i) >> 4;
            int sline = (address + i) % 0x10;
            if (sline < 8)
            {
                this.ptTile[index].setScanline(sline, value_Renamed[offset + i], this.ppuMem.load((address + 8) + i));
            }
            else
            {
                this.ptTile[index].setScanline(sline - 8, this.ppuMem.load((address - 8) + i), value_Renamed[offset + i]);
            }
        }
    }

    public virtual short readStatusRegister()
    {
        this.tmp = this.nes.CpuMemory.load(0x2002);
        this.firstWrite = true;
        this.setStatusFlag(this.STATUS_VBLANK, false);
        return this.tmp;
    }

    private void regsFromAddress()
    {
        this.address = (this.vramTmpAddress >> 8) & 0xff;
        this.regFV = (this.address >> 4) & 7;
        this.regV = (this.address >> 3) & 1;
        this.regH = (this.address >> 2) & 1;
        this.regVT = (this.regVT & 7) | ((this.address & 3) << 3);
        this.address = this.vramTmpAddress & 0xff;
        this.regVT = (this.regVT & 0x18) | ((this.address >> 5) & 7);
        this.regHT = this.address & 0x1f;
    }

    private void regsToAddress()
    {
        this.b1 = (this.regFV & 7) << 4;
        this.b1 |= (this.regV & 1) << 3;
        this.b1 |= (this.regH & 1) << 2;
        this.b1 |= (this.regVT >> 3) & 3;
        this.b2 = (this.regVT & 7) << 5;
        this.b2 |= this.regHT & 0x1f;
        this.vramTmpAddress = ((this.b1 << 8) | this.b2) & 0x7fff;
    }

    private void renderBgScanline(int[] buffer, int scan)
    {
        this.baseTile = (this.regS == 0) ? 0 : 0x100;
        this.destIndex = (scan << 8) - this.regFH;
        this.curNt = this.ntable1[(this.cntV + this.cntV) + this.cntH];
        this.cntHT = this.regHT;
        this.cntH = this.regH;
        this.curNt = this.ntable1[(this.cntV + this.cntV) + this.cntH];
        if ((scan < 240) && ((scan - this.cntFV) >= 0))
        {
            this.tscanoffset = this.cntFV << 3;
            this.y = scan - this.cntFV;
            this.tile = 0;
            while (this.tile < 0x20)
            {
                if (scan >= 0)
                {
                    if (this.validTileData)
                    {
                        this.t = this.scantile[this.tile];
                        this.tpix = this.t.pix;
                        this.att = this.attrib[this.tile];
                    }
                    else
                    {
                        this.t = this.ptTile[this.baseTile + this.nameTable[this.curNt].getTileIndex(this.cntHT, this.cntVT)];
                        this.tpix = this.t.pix;
                        this.att = this.nameTable[this.curNt].getAttrib(this.cntHT, this.cntVT);
                        this.scantile[this.tile] = this.t;
                        this.attrib[this.tile] = this.att;
                    }
                    this.sx = 0;
                    this.x = (this.tile << 3) - this.regFH;
                    if (this.x > -8)
                    {
                        if (this.x < 0)
                        {
                            this.destIndex -= this.x;
                            this.sx = -this.x;
                        }
                        if (!this.t.opaque[this.cntFV])
                        {
                            goto Label_02F6;
                        }
                        while (this.sx < 8)
                        {
                            buffer[this.destIndex] = this.imgPalette[this.tpix[this.tscanoffset + this.sx] + this.att];
                            this.pixrendered[this.destIndex] |= 0x100;
                            this.destIndex++;
                            this.sx++;
                        }
                    }
                }
                goto Label_0302;
            Label_027A:
                this.col = this.tpix[this.tscanoffset + this.sx];
                if (this.col != 0)
                {
                    buffer[this.destIndex] = this.imgPalette[this.col + this.att];
                    this.pixrendered[this.destIndex] |= 0x100;
                }
                this.destIndex++;
                this.sx++;
            Label_02F6:
                if (this.sx < 8)
                {
                    goto Label_027A;
                }
            Label_0302:
                this.cntHT++;
                if (this.cntHT == 0x20)
                {
                    this.cntHT = 0;
                    this.cntH++;
                    this.cntH = this.cntH % 2;
                    this.curNt = this.ntable1[(this.cntV << 1) + this.cntH];
                }
                this.tile++;
            }
            this.validTileData = true;
        }
        this.cntFV++;
        if (this.cntFV == 8)
        {
            this.cntFV = 0;
            this.cntVT++;
            if (this.cntVT == 30)
            {
                this.cntVT = 0;
                this.cntV++;
                this.cntV = this.cntV % 2;
                this.curNt = this.ntable1[(this.cntV << 1) + this.cntH];
            }
            else if (this.cntVT == 0x20)
            {
                this.cntVT = 0;
            }
            this.validTileData = false;
        }
    }

    private void renderFramePartially(int[] buffer, int startScan, int scanCount)
    {
        if ((this.f_spVisibility == 1) && !Globals.disableSprites)
        {
            this.renderSpritesPartially(startScan, scanCount, true);
        }
        if (this.f_bgVisibility == 1)
        {
            this.si = startScan << 8;
            this.ei = (startScan + scanCount) << 8;
            if (this.ei > 0xf000)
            {
                this.ei = 0xf000;
            }
            this.destIndex = this.si;
            while (this.destIndex < this.ei)
            {
                if (this.pixrendered[this.destIndex] > 0xff)
                {
                    buffer[this.destIndex] = this.bgbuffer[this.destIndex];
                }
                this.destIndex++;
            }
        }
        if ((this.f_spVisibility == 1) && !Globals.disableSprites)
        {
            this.renderSpritesPartially(startScan, scanCount, false);
        }
        BufferView screenView = this.nes.Gui.ScreenView;
        if (!this.requestRenderAll)
        {
            if ((startScan + scanCount) > 240)
            {
                scanCount = 240 - startScan;
            }
            for (int i = startScan; i < (startScan + scanCount); i++)
            {
                this.scanlineChanged[i] = false;
                this.si = i << 8;
                int num2 = this.si + 0x100;
                int si = this.si;
                while (si < num2)
                {
                    if (buffer[si] != this.oldFrame[si])
                    {
                        this.scanlineChanged[i] = true;
                        break;
                    }
                    this.oldFrame[si] = buffer[si];
                    si++;
                }
                Array.Copy(buffer, si, this.oldFrame, si, num2 - si);
            }
        }
        this.validTileData = false;
    }

    public virtual void renderNameTables()
    {
        int[] buffer = this.nes.Gui.NameTableView.Buffer;
        if (this.f_bgPatternTable == 0)
        {
            this.baseTile = 0;
        }
        else
        {
            this.baseTile = 0x100;
        }
        int num = 2;
        int num2 = 2;
        if (this.currentMirroring == 1)
        {
            num = 1;
        }
        else if (this.currentMirroring == 0)
        {
            num2 = 1;
        }
        for (int i = 0; i < num2; i++)
        {
            for (int j = 0; j < num; j++)
            {
                int index = this.ntable1[(i * 2) + j];
                int num6 = j * 0x80;
                int num7 = i * 120;
                for (int k = 0; k < 30; k++)
                {
                    for (int m = 0; m < 0x20; m++)
                    {
                        this.ptTile[this.baseTile + this.nameTable[index].getTileIndex(m, k)].renderSmall(num6 + (m * 4), num7 + (k * 4), buffer, this.nameTable[index].getAttrib(m, k), this.imgPalette);
                    }
                }
            }
        }
        if (this.currentMirroring == 1)
        {
            for (int n = 0; n < 240; n++)
            {
                for (int num11 = 0; num11 < 0x80; num11++)
                {
                    buffer[((n << 8) + 0x80) + num11] = buffer[(n << 8) + num11];
                }
            }
        }
        else if (this.currentMirroring == 0)
        {
            for (int num12 = 0; num12 < 120; num12++)
            {
                for (int num13 = 0; num13 < 0x100; num13++)
                {
                    buffer[((num12 << 8) + 0x7800) + num13] = buffer[(num12 << 8) + num13];
                }
            }
        }
        this.nes.Gui.NameTableView.imageReady(false);
    }

    private void renderPalettes()
    {
        int[] buffer = this.nes.Gui.ImgPalView.Buffer;
        for (int i = 0; i < 0x10; i++)
        {
            for (int k = 0; k < 0x10; k++)
            {
                for (int m = 0; m < 0x10; m++)
                {
                    buffer[((k * 0x100) + (i * 0x10)) + m] = this.imgPalette[i];
                }
            }
        }
        buffer = this.nes.Gui.SprPalView.Buffer;
        for (int j = 0; j < 0x10; j++)
        {
            for (int n = 0; n < 0x10; n++)
            {
                for (int num6 = 0; num6 < 0x10; num6++)
                {
                    buffer[((n * 0x100) + (j * 0x10)) + num6] = this.sprPalette[j];
                }
            }
        }
        this.nes.Gui.ImgPalView.imageReady(false);
        this.nes.Gui.SprPalView.imageReady(false);
    }

    public virtual void renderPattern()
    {
        int[] buffer = this.nes.Gui.PatternView.Buffer;
        int index = 0;
        for (int i = 0; i < 2; i++)
        {
            for (int j = 0; j < 0x10; j++)
            {
                for (int k = 0; k < 0x10; k++)
                {
                    this.ptTile[index].renderSimple((i * 0x80) + (k * 8), j * 8, buffer, 0, this.sprPalette);
                    index++;
                }
            }
        }
        this.nes.Gui.PatternView.imageReady(false);
    }

    private void renderSpritesPartially(int startscan, int scancount, bool bgPri)
    {
        this.buffer = this.nes.Gui.ScreenView.Buffer;
        if (this.f_spVisibility == 1)
        {
            for (int i = 0; i < 0x40; i++)
            {
                if ((((this.bgPriority[i] == bgPri) && (this.sprX[i] >= 0)) && ((this.sprX[i] < 0x100) && ((this.sprY[i] + 8) >= startscan))) && (this.sprY[i] < (startscan + scancount)))
                {
                    if (this.f_spriteSize == 0)
                    {
                        this.srcy1 = 0;
                        this.srcy2 = 8;
                        if (this.sprY[i] < startscan)
                        {
                            this.srcy1 = (startscan - this.sprY[i]) - 1;
                        }
                        if ((this.sprY[i] + 8) > (startscan + scancount))
                        {
                            this.srcy2 = ((startscan + scancount) - this.sprY[i]) + 1;
                        }
                        if (this.f_spPatternTable == 0)
                        {
                            this.ptTile[this.sprTile[i]].render(0, this.srcy1, 8, this.srcy2, this.sprX[i], this.sprY[i] + 1, this.buffer, this.sprCol[i], this.sprPalette, this.horiFlip[i], this.vertFlip[i], i, this.pixrendered);
                        }
                        else
                        {
                            this.ptTile[this.sprTile[i] + 0x100].render(0, this.srcy1, 8, this.srcy2, this.sprX[i], this.sprY[i] + 1, this.buffer, this.sprCol[i], this.sprPalette, this.horiFlip[i], this.vertFlip[i], i, this.pixrendered);
                        }
                    }
                    else
                    {
                        int num2 = this.sprTile[i];
                        if ((num2 & 1) != 0)
                        {
                            num2 = (this.sprTile[i] - 1) + 0x100;
                        }
                        this.srcy1 = 0;
                        this.srcy2 = 8;
                        if (this.sprY[i] < startscan)
                        {
                            this.srcy1 = (startscan - this.sprY[i]) - 1;
                        }
                        if ((this.sprY[i] + 8) > (startscan + scancount))
                        {
                            this.srcy2 = (startscan + scancount) - this.sprY[i];
                        }
                        this.ptTile[num2 + (this.vertFlip[i] ? 1 : 0)].render(0, this.srcy1, 8, this.srcy2, this.sprX[i], this.sprY[i] + 1, this.buffer, this.sprCol[i], this.sprPalette, this.horiFlip[i], this.vertFlip[i], i, this.pixrendered);
                        this.srcy1 = 0;
                        this.srcy2 = 8;
                        if ((this.sprY[i] + 8) < startscan)
                        {
                            this.srcy1 = startscan - ((this.sprY[i] + 8) + 1);
                        }
                        if ((this.sprY[i] + 0x10) > (startscan + scancount))
                        {
                            this.srcy2 = (startscan + scancount) - (this.sprY[i] + 8);
                        }
                        this.ptTile[num2 + (this.vertFlip[i] ? 0 : 1)].render(0, this.srcy1, 8, this.srcy2, this.sprX[i], (this.sprY[i] + 1) + 8, this.buffer, this.sprCol[i], this.sprPalette, this.horiFlip[i], this.vertFlip[i], i, this.pixrendered);
                    }
                }
            }
        }
    }

    public virtual void reset()
    {
        this.ppuMem.reset();
        this.sprMem.reset();
        this.vramBufferedReadValue = 0;
        this.sramAddress = 0;
        this.curX = 0;
        this.scanline = 0;
        this.lastRenderedScanline = 0;
        this.spr0HitX = 0;
        this.spr0HitY = 0;
        this.mapperIrqCounter = 0;
        this.currentMirroring = -1;
        this.firstWrite = true;
        this.requestEndFrame = false;
        this.nmiOk = false;
        this.hitSpr0 = false;
        this.dummyCycleToggle = false;
        this.validTileData = false;
        this.nmiCounter = 0;
        this.tmp = 0;
        this.att = 0;
        this.i = 0;
        this.f_nmiOnVblank = 0;
        this.f_spriteSize = 0;
        this.f_bgPatternTable = 0;
        this.f_spPatternTable = 0;
        this.f_addrInc = 0;
        this.f_nTblAddress = 0;
        this.f_color = 0;
        this.f_spVisibility = 0;
        this.f_bgVisibility = 0;
        this.f_spClipping = 0;
        this.f_bgClipping = 0;
        this.f_dispType = 0;
        this.cntFV = 0;
        this.cntV = 0;
        this.cntH = 0;
        this.cntVT = 0;
        this.cntHT = 0;
        this.regFV = 0;
        this.regV = 0;
        this.regH = 0;
        this.regVT = 0;
        this.regHT = 0;
        this.regFH = 0;
        this.regS = 0;
        SupportClass.ArraySupport.Fill(this.scanlineChanged, true);
        SupportClass.ArraySupport.Fill(this.oldFrame, -1);
        this.init();
    }

    public virtual void scrollWrite(short value_Renamed)
    {
        this.triggerRendering();
        if (this.firstWrite)
        {
            this.regHT = (value_Renamed >> 3) & 0x1f;
            this.regFH = value_Renamed & 7;
        }
        else
        {
            this.regFV = value_Renamed & 7;
            this.regVT = (value_Renamed >> 3) & 0x1f;
        }
        this.firstWrite = !this.firstWrite;
    }

    public virtual void setStatusFlag(int flag, bool value_Renamed)
    {
        int num = ((int) 1) << flag;
        int num2 = (this.nes.CpuMemory.load(0x2002) & (0xff - num)) | (value_Renamed ? num : 0);
        this.nes.CpuMemory.write(0x2002, (short) num2);
    }

    public virtual void spriteRamWriteUpdate(int address, short value_Renamed)
    {
        int index = address / 4;
        if (index == 0)
        {
            this.checkSprite0(((this.scanline + 1) - this.vblankAdd) - 0x15);
        }
        if ((address % 4) == 0)
        {
            this.sprY[index] = value_Renamed;
        }
        else if ((address % 4) == 1)
        {
            this.sprTile[index] = value_Renamed;
        }
        else if ((address % 4) == 2)
        {
            this.vertFlip[index] = (value_Renamed & 0x80) != 0;
            this.horiFlip[index] = (value_Renamed & 0x40) != 0;
            this.bgPriority[index] = (value_Renamed & 0x20) != 0;
            this.sprCol[index] = (value_Renamed & 3) << 2;
        }
        else if ((address % 4) == 3)
        {
            this.sprX[index] = value_Renamed;
        }
    }

    public virtual void sramDMA(short value_Renamed)
    {
        Memory cpuMemory = this.nes.CpuMemory;
        int num = value_Renamed * 0x100;
        for (int i = this.sramAddress; i < 0x100; i++)
        {
            short num2 = cpuMemory.load(num + i);
            this.sprMem.write(i, num2);
            this.spriteRamWriteUpdate(i, num2);
        }
        this.nes.Cpu.haltCycles(0x201);
    }

    public virtual short sramLoad()
    {
        return this.sprMem.load(this.sramAddress);
    }

    public virtual void sramWrite(short value_Renamed)
    {
        this.sprMem.write(this.sramAddress, value_Renamed);
        this.spriteRamWriteUpdate(this.sramAddress, value_Renamed);
        this.sramAddress = (short) (this.sramAddress + 1);
        this.sramAddress = (short) (this.sramAddress % 0x100);
    }

    public virtual void startFrame()
    {
        int num2;
        int[] buffer = this.nes.Gui.ScreenView.Buffer;
        int num = 0;
        if (this.f_dispType == 0)
        {
            num = this.imgPalette[0];
            goto Label_006A;
        }
        switch (this.f_color)
        {
            case 0:
                num = 0;
                goto Label_006A;

            case 1:
                num = 0xff00;
                break;

            case 2:
                break;

            case 3:
                goto Label_0060;

            case 4:
                goto Label_0062;

            default:
                goto Label_0068;
        }
        num = 0xff0000;
    Label_0060:
        num = 0;
    Label_0062:
        num = 0xff;
    Label_0068:
        num = 0;
    Label_006A:
        num2 = 0;
        while (num2 < buffer.Length)
        {
            buffer[num2] = num;
            num2++;
        }
        for (int i = 0; i < this.pixrendered.Length; i++)
        {
            this.pixrendered[i] = 0x41;
        }
    }

    public virtual void startVBlank()
    {
        this.nes.Cpu.requestIrq(1);
        if (this.lastRenderedScanline < 0xef)
        {
            this.renderFramePartially(this.nes.gui.ScreenView.Buffer, this.lastRenderedScanline + 1, 240 - this.lastRenderedScanline);
        }
        this.endFrame();
        this.nes.Gui.ScreenView.imageReady(false);
        this.lastRenderedScanline = -1;
        this.startFrame();
    }

    public virtual void stateLoad(ByteBuffer buf)
    {
        if (buf.readByte() == 1)
        {
            this.cntFV = buf.readInt();
            this.cntV = buf.readInt();
            this.cntH = buf.readInt();
            this.cntVT = buf.readInt();
            this.cntHT = buf.readInt();
            this.regFV = buf.readInt();
            this.regV = buf.readInt();
            this.regH = buf.readInt();
            this.regVT = buf.readInt();
            this.regHT = buf.readInt();
            this.regFH = buf.readInt();
            this.regS = buf.readInt();
            this.vramAddress = buf.readInt();
            this.vramTmpAddress = buf.readInt();
            this.statusRegsFromInt(buf.readInt());
            this.vramBufferedReadValue = (short) buf.readInt();
            this.firstWrite = buf.readBoolean();
            this.currentMirroring = buf.readInt();
            for (int i = 0; i < this.vramMirrorTable.Length; i++)
            {
                this.vramMirrorTable[i] = buf.readInt();
            }
            this.sramAddress = (short) buf.readInt();
            this.curX = buf.readInt();
            this.scanline = buf.readInt();
            this.lastRenderedScanline = buf.readInt();
            this.requestEndFrame = buf.readBoolean();
            this.nmiOk = buf.readBoolean();
            this.dummyCycleToggle = buf.readBoolean();
            this.nmiCounter = buf.readInt();
            this.tmp = (short) buf.readInt();
            for (int j = 0; j < this.bgbuffer.Length; j++)
            {
                this.bgbuffer[j] = buf.readInt();
            }
            for (int k = 0; k < this.pixrendered.Length; k++)
            {
                this.pixrendered[k] = buf.readInt();
            }
            for (int m = 0; m < 4; m++)
            {
                this.ntable1[m] = buf.readInt();
                this.nameTable[m].stateLoad(buf);
            }
            for (int n = 0; n < this.ptTile.Length; n++)
            {
                this.ptTile[n].stateLoad(buf);
            }
            short[] mem = this.nes.SprMemory.mem;
            for (int num6 = 0; num6 < mem.Length; num6++)
            {
                this.spriteRamWriteUpdate(num6, mem[num6]);
            }
        }
    }

    public virtual void stateSave(ByteBuffer buf)
    {
        buf.putByte(1);
        buf.putInt(this.cntFV);
        buf.putInt(this.cntV);
        buf.putInt(this.cntH);
        buf.putInt(this.cntVT);
        buf.putInt(this.cntHT);
        buf.putInt(this.regFV);
        buf.putInt(this.regV);
        buf.putInt(this.regH);
        buf.putInt(this.regVT);
        buf.putInt(this.regHT);
        buf.putInt(this.regFH);
        buf.putInt(this.regS);
        buf.putInt(this.vramAddress);
        buf.putInt(this.vramTmpAddress);
        buf.putInt(this.statusRegsToInt());
        buf.putInt(this.vramBufferedReadValue);
        buf.putBoolean(this.firstWrite);
        buf.putInt(this.currentMirroring);
        for (int i = 0; i < this.vramMirrorTable.Length; i++)
        {
            buf.putInt(this.vramMirrorTable[i]);
        }
        buf.putInt(this.sramAddress);
        buf.putInt(this.curX);
        buf.putInt(this.scanline);
        buf.putInt(this.lastRenderedScanline);
        buf.putBoolean(this.requestEndFrame);
        buf.putBoolean(this.nmiOk);
        buf.putBoolean(this.dummyCycleToggle);
        buf.putInt(this.nmiCounter);
        buf.putInt(this.tmp);
        for (int j = 0; j < this.bgbuffer.Length; j++)
        {
            buf.putInt(this.bgbuffer[j]);
        }
        for (int k = 0; k < this.pixrendered.Length; k++)
        {
            buf.putInt(this.pixrendered[k]);
        }
        for (int m = 0; m < 4; m++)
        {
            buf.putInt(this.ntable1[m]);
            this.nameTable[m].stateSave(buf);
        }
        for (int n = 0; n < this.ptTile.Length; n++)
        {
            this.ptTile[n].stateSave(buf);
        }
    }

    public virtual void statusRegsFromInt(int n)
    {
        this.f_nmiOnVblank = n & 1;
        this.f_spriteSize = (n >> 1) & 1;
        this.f_bgPatternTable = (n >> 2) & 1;
        this.f_spPatternTable = (n >> 3) & 1;
        this.f_addrInc = (n >> 4) & 1;
        this.f_nTblAddress = (n >> 5) & 1;
        this.f_color = (n >> 6) & 1;
        this.f_spVisibility = (n >> 7) & 1;
        this.f_bgVisibility = (n >> 8) & 1;
        this.f_spClipping = (n >> 9) & 1;
        this.f_bgClipping = (n >> 10) & 1;
        this.f_dispType = (n >> 11) & 1;
    }

    public virtual int statusRegsToInt()
    {
        return (((((((((((this.f_nmiOnVblank | (this.f_spriteSize << 1)) | (this.f_bgPatternTable << 2)) | (this.f_spPatternTable << 3)) | (this.f_addrInc << 4)) | (this.f_nTblAddress << 5)) | (this.f_color << 6)) | (this.f_spVisibility << 7)) | (this.f_bgVisibility << 8)) | (this.f_spClipping << 9)) | (this.f_bgClipping << 10)) | (this.f_dispType << 11));
    }

    public virtual void triggerRendering()
    {
        if (((this.scanline - this.vblankAdd) >= 0x15) && ((this.scanline - this.vblankAdd) <= 260))
        {
            this.renderFramePartially(this.buffer, this.lastRenderedScanline + 1, ((this.scanline - this.vblankAdd) - 0x15) - this.lastRenderedScanline);
            this.lastRenderedScanline = (this.scanline - this.vblankAdd) - 0x15;
        }
    }

    public virtual void updateControlReg1(int value_Renamed)
    {
        this.triggerRendering();
        this.f_nmiOnVblank = (value_Renamed >> 7) & 1;
        this.f_spriteSize = (value_Renamed >> 5) & 1;
        this.f_bgPatternTable = (value_Renamed >> 4) & 1;
        this.f_spPatternTable = (value_Renamed >> 3) & 1;
        this.f_addrInc = (value_Renamed >> 2) & 1;
        this.f_nTblAddress = value_Renamed & 3;
        this.regV = (value_Renamed >> 1) & 1;
        this.regH = value_Renamed & 1;
        this.regS = (value_Renamed >> 4) & 1;
    }

    public virtual void updateControlReg2(int value_Renamed)
    {
        this.triggerRendering();
        this.f_color = (value_Renamed >> 5) & 7;
        this.f_spVisibility = (value_Renamed >> 4) & 1;
        this.f_bgVisibility = (value_Renamed >> 3) & 1;
        this.f_spClipping = (value_Renamed >> 2) & 1;
        this.f_bgClipping = (value_Renamed >> 1) & 1;
        this.f_dispType = value_Renamed & 1;
        if (this.f_dispType == 0)
        {
            this.nes.palTable.setEmphasis(this.f_color);
        }
        this.updatePalettes();
    }

    public virtual void updatePalettes()
    {
        for (int i = 0; i < 0x10; i++)
        {
            if (this.f_dispType == 0)
            {
                this.imgPalette[i] = this.nes.palTable.getEntry(this.ppuMem.load(0x3f00 + i) & 0x3f);
            }
            else
            {
                this.imgPalette[i] = this.nes.palTable.getEntry(this.ppuMem.load(0x3f00 + i) & 0x20);
            }
        }
        for (int j = 0; j < 0x10; j++)
        {
            if (this.f_dispType == 0)
            {
                this.sprPalette[j] = this.nes.palTable.getEntry(this.ppuMem.load(0x3f10 + j) & 0x3f);
            }
            else
            {
                this.sprPalette[j] = this.nes.palTable.getEntry(this.ppuMem.load(0x3f10 + j) & 0x20);
            }
        }
    }

    public virtual short vramLoad()
    {
        this.cntsToAddress();
        this.regsToAddress();
        if (this.vramAddress <= 0x3eff)
        {
            short vramBufferedReadValue = this.vramBufferedReadValue;
            if (this.vramAddress < 0x2000)
            {
                this.vramBufferedReadValue = this.ppuMem.load(this.vramAddress);
            }
            else
            {
                this.vramBufferedReadValue = this.mirroredLoad(this.vramAddress);
            }
            if (this.vramAddress < 0x2000)
            {
                this.nes.memMapper.latchAccess(this.vramAddress);
            }
            this.vramAddress += (this.f_addrInc == 1) ? 0x20 : 1;
            this.cntsFromAddress();
            this.regsFromAddress();
            return vramBufferedReadValue;
        }
        short num2 = this.mirroredLoad(this.vramAddress);
        this.vramAddress += (this.f_addrInc == 1) ? 0x20 : 1;
        this.cntsFromAddress();
        this.regsFromAddress();
        return num2;
    }

    public virtual void vramWrite(short value_Renamed)
    {
        this.triggerRendering();
        this.cntsToAddress();
        this.regsToAddress();
        if (this.vramAddress >= 0x2000)
        {
            this.mirroredWrite(this.vramAddress, value_Renamed);
        }
        else
        {
            this.writeMem(this.vramAddress, value_Renamed);
            this.nes.memMapper.latchAccess(this.vramAddress);
        }
        this.vramAddress += (this.f_addrInc == 1) ? 0x20 : 1;
        this.regsFromAddress();
        this.cntsFromAddress();
    }

    private void writeMem(int address, short value_Renamed)
    {
        this.ppuMem.write(address, value_Renamed);
        if (address < 0x2000)
        {
            this.ppuMem.write(address, value_Renamed);
            this.patternWrite(address, value_Renamed);
        }
        else if ((address >= 0x2000) && (address < 0x23c0))
        {
            this.nameTableWrite(this.ntable1[0], address - 0x2000, value_Renamed);
        }
        else if ((address >= 0x23c0) && (address < 0x2400))
        {
            this.attribTableWrite(this.ntable1[0], address - 0x23c0, value_Renamed);
        }
        else if ((address >= 0x2400) && (address < 0x27c0))
        {
            this.nameTableWrite(this.ntable1[1], address - 0x2400, value_Renamed);
        }
        else if ((address >= 0x27c0) && (address < 0x2800))
        {
            this.attribTableWrite(this.ntable1[1], address - 0x27c0, value_Renamed);
        }
        else if ((address >= 0x2800) && (address < 0x2bc0))
        {
            this.nameTableWrite(this.ntable1[2], address - 0x2800, value_Renamed);
        }
        else if ((address >= 0x2bc0) && (address < 0x2c00))
        {
            this.attribTableWrite(this.ntable1[2], address - 0x2bc0, value_Renamed);
        }
        else if ((address >= 0x2c00) && (address < 0x2fc0))
        {
            this.nameTableWrite(this.ntable1[3], address - 0x2c00, value_Renamed);
        }
        else if ((address >= 0x2fc0) && (address < 0x3000))
        {
            this.attribTableWrite(this.ntable1[3], address - 0x2fc0, value_Renamed);
        }
        else if ((address >= 0x3f00) && (address < 0x3f20))
        {
            this.updatePalettes();
        }
    }

    public virtual void writeSRAMAddress(short address)
    {
        this.sramAddress = address;
    }

    public virtual void writeVRAMAddress(int address)
    {
        if (this.firstWrite)
        {
            this.regFV = (address >> 4) & 3;
            this.regV = (address >> 3) & 1;
            this.regH = (address >> 2) & 1;
            this.regVT = (this.regVT & 7) | ((address & 3) << 3);
        }
        else
        {
            this.triggerRendering();
            this.regVT = (this.regVT & 0x18) | ((address >> 5) & 7);
            this.regHT = address & 0x1f;
            this.cntFV = this.regFV;
            this.cntV = this.regV;
            this.cntH = this.regH;
            this.cntVT = this.regVT;
            this.cntHT = this.regHT;
            this.checkSprite0(((this.scanline - this.vblankAdd) + 1) - 0x15);
        }
        this.firstWrite = !this.firstWrite;
        this.cntsToAddress();
        if (this.vramAddress < 0x2000)
        {
            this.nes.memMapper.latchAccess(this.vramAddress);
        }
    }

    public virtual int Mirroring
    {
        set
        {
            if (value != this.currentMirroring)
            {
                this.currentMirroring = value;
                this.triggerRendering();
                if (this.vramMirrorTable == null)
                {
                    this.vramMirrorTable = new int[0x8000];
                }
                for (int i = 0; i < 0x8000; i++)
                {
                    this.vramMirrorTable[i] = i;
                }
                this.defineMirrorRegion(0x3f20, 0x3f00, 0x20);
                this.defineMirrorRegion(0x3f40, 0x3f00, 0x20);
                this.defineMirrorRegion(0x3f80, 0x3f00, 0x20);
                this.defineMirrorRegion(0x3fc0, 0x3f00, 0x20);
                this.defineMirrorRegion(0x3000, 0x2000, 0xf00);
                this.defineMirrorRegion(0x4000, 0, 0x4000);
                if (value == 1)
                {
                    this.ntable1[0] = 0;
                    this.ntable1[1] = 0;
                    this.ntable1[2] = 1;
                    this.ntable1[3] = 1;
                    this.defineMirrorRegion(0x2400, 0x2000, 0x400);
                    this.defineMirrorRegion(0x2c00, 0x2800, 0x400);
                }
                else if (value == 0)
                {
                    this.ntable1[0] = 0;
                    this.ntable1[1] = 1;
                    this.ntable1[2] = 0;
                    this.ntable1[3] = 1;
                    this.defineMirrorRegion(0x2800, 0x2000, 0x400);
                    this.defineMirrorRegion(0x2c00, 0x2400, 0x400);
                }
                else if (value == 3)
                {
                    this.ntable1[0] = 0;
                    this.ntable1[1] = 0;
                    this.ntable1[2] = 0;
                    this.ntable1[3] = 0;
                    this.defineMirrorRegion(0x2400, 0x2000, 0x400);
                    this.defineMirrorRegion(0x2800, 0x2000, 0x400);
                    this.defineMirrorRegion(0x2c00, 0x2000, 0x400);
                }
                else if (value == 4)
                {
                    this.ntable1[0] = 1;
                    this.ntable1[1] = 1;
                    this.ntable1[2] = 1;
                    this.ntable1[3] = 1;
                    this.defineMirrorRegion(0x2400, 0x2400, 0x400);
                    this.defineMirrorRegion(0x2800, 0x2400, 0x400);
                    this.defineMirrorRegion(0x2c00, 0x2400, 0x400);
                }
                else
                {
                    this.ntable1[0] = 0;
                    this.ntable1[1] = 1;
                    this.ntable1[2] = 2;
                    this.ntable1[3] = 3;
                }
            }
        }
    }
}

